checkasm: Trim out unused upstream files

We only need the source files themselves; keep them and the
LICENSE file.
This commit is contained in:
Martin Storsjö
2026-06-10 21:54:05 +03:00
parent d5d62e04b9
commit 93ebde18e1
45 changed files with 0 additions and 5993 deletions

View File

@@ -1,23 +0,0 @@
---
BasedOnStyle: WebKit
IndentWidth: 4
IndentPPDirectives: BeforeHash
PPIndentWidth: 2
ColumnLimit: 90
AllowShortFunctionsOnASingleLine: false
AllowShortLoopsOnASingleLine: false
AllowShortCaseLabelsOnASingleLine: true
AlignConsecutiveDeclarations: true
AlignConsecutiveAssignments: true
AlignConsecutiveMacros: true
AlignOperands: AlignAfterOperator
AlignEscapedNewlines: Right
AlignAfterOpenBracket: Align
AlignArrayOfStructures: Left
AlignTrailingComments: true
AlignConsecutiveBitFields: true
AlignConsecutiveShortCaseStatements:
Enabled: true
PointerAlignment: Right
SpaceAfterCStyleCast: true
QualifierAlignment: Left

View File

@@ -1,228 +0,0 @@
name: Run tests with MSVC
on:
push:
jobs:
msvc-x86:
runs-on: windows-latest
strategy:
fail-fast: false
matrix:
arch:
- x86
- x64
steps:
- name: Install prerequisites
run: |
pip install meson
choco install nasm
echo "C:\Program Files\NASM" | Out-File -FilePath $Env:GITHUB_PATH -Encoding utf8 -Append
- name: Set up the MSVC environment
uses: ilammy/msvc-dev-cmd@v1
with:
arch: ${{matrix.arch}}
- uses: actions/checkout@v6
- name: Build and test
run: |
meson setup build --buildtype release `
--werror `
-Ddefault_library=both
cd build
ninja
meson test -v
msvc-cross-arm64:
# Test cross compilation with MSVC; here, we don't have any working
# host compiler available, as the only compiler in $PATH is a cross
# compiler.
runs-on: windows-latest
steps:
- name: Install prerequisites
run: |
pip install meson
- name: Set up the MSVC environment
uses: ilammy/msvc-dev-cmd@v1
with:
arch: amd64_arm64
- uses: actions/checkout@v6
- name: Checkout gas-preprocessor
uses: actions/checkout@v6
with:
repository: ffmpeg/gas-preprocessor
ref: f85b94087d168564b777877dfb3a0eeea3d57678
path: gas-preprocessor
- name: Add gas-preprocessor to path
run: |
echo "$PWD\gas-preprocessor" | Out-File -FilePath $Env:GITHUB_PATH -Encoding utf8 -Append
- name: Create cross file
shell: bash
run: |
cat >cross.txt <<EOF
[binaries]
c = 'cl'
cpp = 'cl'
ar = 'lib'
windres = 'rc'
[properties]
needs_exe_wrapper = true
[host_machine]
system = 'windows'
cpu_family = 'aarch64'
cpu = 'aarch64'
endian = 'little'
EOF
- name: Build and test
run: |
meson setup build --cross-file cross.txt --buildtype release `
--werror `
-Ddefault_library=both
cd build
ninja
meson test -v
msvc-arm64:
runs-on: windows-11-arm
strategy:
fail-fast: false
matrix:
uwp:
- false
- true
steps:
- name: Install prerequisites
run: |
pip install meson
- name: Set up the MSVC environment
uses: ilammy/msvc-dev-cmd@v1
with:
# The preinstalled versions of WinSDK no longer contain support
# for ARM, only ARM64
arch: arm64
- uses: actions/checkout@v6
- name: Checkout gas-preprocessor
uses: actions/checkout@v6
with:
repository: ffmpeg/gas-preprocessor
ref: f85b94087d168564b777877dfb3a0eeea3d57678
path: gas-preprocessor
- name: Add gas-preprocessor to path
run: |
echo "$PWD\gas-preprocessor" | Out-File -FilePath $Env:GITHUB_PATH -Encoding utf8 -Append
- name: Set CFLAGS
if: matrix.uwp
run: |
echo "CFLAGS=-DWINAPI_FAMILY=WINAPI_FAMILY_PHONE_APP" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf8 -Append
- name: Build and test
run: |
meson setup build --buildtype release `
--werror `
-Ddefault_library=both
cd build
ninja
meson test -v
clangcl-arm64:
runs-on: windows-11-arm
steps:
- name: Install prerequisites
run: |
pip install meson
- name: Set up the MSVC environment
uses: ilammy/msvc-dev-cmd@v1
with:
arch: arm64
- uses: actions/checkout@v6
- name: Build and test
run: |
$Env:CC = "clang-cl"
meson setup build --buildtype release `
--werror `
-Ddefault_library=both
cd build
ninja
meson test -v
msvc-wine:
runs-on: ubuntu-24.04-arm
container: ghcr.io/mstorsjo/wine
strategy:
fail-fast: false
matrix:
arch:
- arm
- arm64
steps:
- name: Cache MSVC
id: cache-msvc
uses: actions/cache@v4
with:
path: /opt/msvc
key: msvc-arm64-17.13
- name: Checkout msvc-wine
if: ${{ !steps.cache-msvc.outputs.cache-hit }}
uses: actions/checkout@v6
with:
repository: mstorsjo/msvc-wine
ref: 91759aa0131a166f17602f81012737a6f353d608
path: msvc-wine
- name: Install msvc-wine
if: ${{ !steps.cache-msvc.outputs.cache-hit }}
run: |
apt-get update && apt-get install -y --no-install-recommends python3 msitools ca-certificates winbind
wine wineboot
cd msvc-wine
# This is the latest WinSDK that contains support for ARM
./vsdownload.py --accept-license --dest /opt/msvc --sdk-version 10.0.22621 --msvc-version 17.13
./install.sh /opt/msvc
- name: Install prerequisites
run: |
apt-get update && apt-get install -y --no-install-recommends meson
- uses: actions/checkout@v6
- name: Checkout gas-preprocessor
uses: actions/checkout@v6
with:
repository: ffmpeg/gas-preprocessor
ref: f85b94087d168564b777877dfb3a0eeea3d57678
path: gas-preprocessor
- name: Build and test
run: |
export PATH=$(pwd)/gas-preprocessor:/opt/msvc/bin/${{matrix.arch}}:$PATH
arch=${{matrix.arch}}
case $arch in
arm)
cpu=armv7
cpu_family=arm
;;
arm64)
cpu=aarch64
cpu_family=aarch64
;;
esac
cat >cross.txt <<EOF
[binaries]
c = 'cl'
cpp = 'cl'
ar = 'lib'
windres = 'rc'
exe_wrapper = ['wine']
[properties]
needs_exe_wrapper = true
[host_machine]
system = 'windows'
cpu_family = '$cpu_family'
cpu = '$cpu'
endian = 'little'
EOF
meson setup build --cross-file cross.txt \
--werror \
-Ddefault_library=both
cd build
ninja
meson test -v

View File

@@ -1,3 +0,0 @@
/checkasm-selftest
*.exe
*.o

View File

@@ -1,441 +0,0 @@
stages:
- style
- build
- test
- bench
- deploy
.debian-amd64-common:
image: registry.videolan.org/dav1d-debian-unstable:20251231153127
stage: test
tags:
- docker
- amd64
.debian-llvm-mingw-common:
image: registry.videolan.org/vlc-debian-llvm-msvcrt:20250305204125
stage: test
tags:
- docker
- amd64
.debian-aarch64-common:
image: registry.videolan.org/dav1d-debian-bookworm-aarch64:20250215002814
stage: test
tags:
- docker
- aarch64
.debian-armv7-common:
image: registry.videolan.org/dav1d-debian-bookworm-armv7:20250215014239
stage: test
tags:
- docker
- armv7
.debian-ppc64le-common:
image: registry.videolan.org/dav1d-debian-unstable-ppc64le:20250215003029
stage: test
tags:
- docker
- ppc64le
.android-common:
image: registry.videolan.org/vlc-debian-android:20241118101328
stage: build
tags:
- docker
- amd64
style-check:
extends: .debian-amd64-common
stage: style
script:
- git grep -I -n -P "\t|\r| $" -- . ':(exclude)*/compat/*' ':(exclude)ext-build-test.make' && echo "Trailing whitespace" && exit 1
- git grep -I -l -z "" -- . ':(exclude)*/compat/*' | while IFS= read -r -d '' i; do
if [ -n "$(tail -c 1 "$i")" ]; then
echo "No newline at end of $i";
exit 1;
fi;
done
- rg '[\u061c\u2000-\u200f\u2028-\u202f\u205f-\u206f]' ./ && echo "Invisible Unicode characters" && exit 1
- git remote rm upstream 2> /dev/null || true
- git remote add upstream https://code.videolan.org/videolan/checkasm.git
- git fetch -q upstream master
- for i in $(git rev-list HEAD ^upstream/master); do
echo "Checking commit message of $i";
msg="$(git log --format=%B -n 1 $i)";
if [ -n "$(echo "$msg" | awk "NR==2")" ]; then
echo "Malformed commit message in $i, second line must be empty";
exit 1;
fi;
if echo "$msg" | head -1 | grep -q '\.$'; then
echo "Malformed commit message in $i, trailing period in subject line";
exit 1;
fi;
done
x86inc-check:
extends: .debian-amd64-common
stage: style
script:
- git remote rm x86inc 2> /dev/null || true
- git remote add x86inc https://code.videolan.org/videolan/x86inc.asm.git
- git fetch -q x86inc master
- git diff --exit-code x86inc/master:x86inc.asm src/x86/x86inc.asm
allow_failure: true
build-docs:
extends: .debian-amd64-common
stage: build
artifacts:
paths:
- build/docs/html
expire_in: 1 day
script:
- meson setup build -Ddocs=enabled
- cd build && meson compile docs
.test-common:
script:
- meson setup build --buildtype release
--default-library both
--werror
- cd build && ninja
- meson test -v
- time meson test --bench -v
test-debian:
extends:
- .test-common
- .debian-amd64-common
tags:
- docker
- avx2
- amd64
artifacts:
paths:
- build/
expire_in: 1 day
test-debian-static:
extends: .debian-amd64-common
script:
- meson setup build --buildtype release
--default-library static
--werror
- cd build && ninja
- meson test -v
- time meson test --bench -v
- nm -A -g src/libcheckasm.a | grep " [ABCDGRST] " | (! grep -v " _*checkasm_")
test-debian-notests:
extends: .debian-amd64-common
script:
- meson setup build --buildtype release
--default-library shared
-Dtests=disabled
--werror
- cd build && ninja
- meson test -v
test-debian32:
extends: .debian-amd64-common
script:
- meson setup build --buildtype release
--werror
--cross-file crossfiles/i686-linux32.meson
- cd build && ninja
- meson test -v
- time meson test --bench -v
artifacts:
paths:
- build/
expire_in: 1 day
test-debian-avx:
extends:
- .test-common
- .debian-amd64-common
tags:
- docker
- avx2
- amd64
variables:
CFLAGS: '-mavx'
test-debian-avx512:
extends:
- .test-common
- .debian-amd64-common
tags:
- docker
- amd64-avx512
variables:
CFLAGS: '-mavx'
test-debian-clang:
extends:
- .test-common
- .debian-amd64-common
variables:
CC: clang
CC_LD: mold
test-debian-qemu:
extends: .debian-amd64-common
tags:
- docker
- avx2
- amd64
script:
- meson setup build --buildtype debug
- cd build && ninja
- exit_code=0
- meson test -v --wrapper 'qemu-x86_64 -cpu Conroe' || exit_code=$((exit_code + $?))
- meson test -v --wrapper 'qemu-x86_64 -cpu Penryn' || exit_code=$((exit_code + $?))
- time meson test --bench -v --wrapper 'qemu-x86_64 -cpu Conroe' || exit_code=$((exit_code + $?))
- time meson test --bench -v --wrapper 'qemu-x86_64 -cpu Penryn' || exit_code=$((exit_code + $?))
- if [ $exit_code -ne 0 ]; then exit $exit_code; fi
test-win:
extends: .debian-amd64-common
script:
- wineserver -p && wine wineboot
- meson setup build --buildtype release
--werror
--libdir lib
--prefix "$(pwd)/build/checkasm_install"
--cross-file crossfiles/${CROSSFILE}.meson
--default-library both
- cd build && ninja install
- meson test -v
- time meson test --bench -v
- ${CROSSFILE}-nm -A -g src/libcheckasm.a | grep " [ABCDGRST] " | (! grep -E -v " \.| _*checkasm_")
artifacts:
name: "$CI_JOB_NAME-$CI_COMMIT_REF_SLUG"
paths:
- build/checkasm_install/
expire_in: 1 week
parallel:
matrix:
- CROSSFILE: [i686-w64-mingw32, x86_64-w64-mingw32]
build-win-arm:
extends: .debian-llvm-mingw-common
stage: build
script:
- meson setup build --buildtype release
--werror
--libdir lib
--prefix "$(pwd)/build/checkasm_install"
--cross-file crossfiles/${CROSSFILE}.meson
--default-library both
- cd build && ninja install
- ${CROSSFILE}-nm -A -g src/libcheckasm.a | grep " [ABCDGRST] " | (! grep -E -v " \.| _*checkasm_")
artifacts:
name: "$CI_JOB_NAME-$CI_COMMIT_REF_SLUG"
paths:
- build/checkasm_install/
expire_in: 1 week
parallel:
matrix:
- CROSSFILE: [armv7-w64-mingw32, aarch64-w64-mingw32]
.build-android-common:
extends: .android-common
stage: build
script:
- meson setup build --buildtype release
--werror
--libdir lib
--prefix "$(pwd)/build/checkasm_install"
--cross-file $CROSSFILE
--default-library both
- cd build && ninja install
build-android-armv7:
extends: .build-android-common
variables:
CROSSFILE: crossfiles/arm-android.meson
rules:
- if: '$CI_COMMIT_BRANCH'
build-android-aarch64:
extends: .build-android-common
variables:
CROSSFILE: crossfiles/aarch64-android.meson
rules:
- if: '$CI_COMMIT_BRANCH'
test-debian-aarch64:
extends:
- .test-common
- .debian-aarch64-common
test-debian-aarch64-clang-5:
extends:
- .test-common
- .debian-aarch64-common
variables:
CC: clang-5.0
CFLAGS: '-integrated-as'
test-debian-aarch64-clang-18:
extends: .debian-amd64-common
variables:
QEMU_LD_PREFIX: /usr/aarch64-linux-gnu/
script:
- meson setup build --buildtype release
--werror
--cross-file crossfiles/aarch64-linux-clang.meson
- cd build && ninja
- meson test -v
- time meson test --bench -v
.test-macos-common:
stage: test
script:
- meson setup build --buildtype release
--default-library both
--werror
- cd build && ninja
- meson test -v
- time meson test --bench -v
test-macos-x86_64:
extends: .test-macos-common
tags:
- amd64
- macos
test-macos-arm64:
extends: .test-macos-common
tags:
- macos-xcode26
test-debian-armv7:
extends: .debian-armv7-common
script:
- linux32 meson setup build --buildtype release
--werror
- cd build && ninja
- meson test -v
- time meson test --bench -v
test-debian-armv7-clang-5:
extends: .debian-armv7-common
variables:
CC: clang-5.0
CFLAGS: '-integrated-as'
script:
- linux32 meson setup build --buildtype release
- cd build && ninja
- meson test -v
- time meson test --bench -v
test-debian-ppc64le:
extends:
- .test-common
- .debian-ppc64le-common
variables:
CC: gcc-13
test-debian-riscv64:
extends: .debian-amd64-common
variables:
QEMU_LD_PREFIX: /usr/riscv64-linux-gnu/
script:
- meson setup build --buildtype release
--default-library both
--werror
--cross-file crossfiles/${CROSSFILE}.meson
- cd build && ninja
- meson test -v
- time meson test --bench -v
parallel:
matrix:
- CROSSFILE: [ riscv64-linux, riscv64-linux-clang ]
QEMU_CPU:
- rv64,v=true,vext_spec=v1.0,vlen=128,elen=64
- rv64,v=true,vext_spec=v1.0,vlen=256,elen=64
test-debian-loongarch64:
extends:
- .debian-amd64-common
variables:
QEMU_CPU: max-loongarch-cpu
QEMU_LD_PREFIX: /opt/cross-tools/target/
script:
- meson setup build --buildtype release
--default-library both
--werror
--cross-file crossfiles/loongarch64-linux.meson
- cd build && ninja
- meson test -v
- time meson test --bench -v
test-debian-asan:
extends: .debian-amd64-common
variables:
ASAN_OPTIONS: 'detect_leaks=0'
script:
- meson setup build --buildtype debugoptimized
-Db_sanitize=address
- cd build && ninja
- exit_code=0
- meson test -v || exit_code=$((exit_code + $?))
- time meson test --bench -v || exit_code=$((exit_code + $?))
- if [ $exit_code -ne 0 ]; then exit $exit_code; fi
test-ext-build:
extends: .debian-amd64-common
script:
- mkdir -p build && cd build
- make -f ../ext-build-test.make NASM_FMT=elf64 -j$(getconf _NPROCESSORS_ONLN) EXTRA_CFLAGS=-Werror
- ./checkasm-selftest
test-dav1d:
extends: .debian-amd64-common
script:
- git clone --depth 1 --branch master https://code.videolan.org/videolan/dav1d.git
- cd dav1d
- ln -s ../.. subprojects/checkasm
- meson setup build --buildtype debugoptimized
--werror
-Dtrim_dsp=false
- cd build && ninja
- meson test -v checkasm
test-dav2d:
extends: .debian-amd64-common
script:
- git clone --depth 1 --branch main https://code.videolan.org/videolan/dav2d.git
- cd dav2d
- ln -s ../.. subprojects/checkasm
- meson setup build --buildtype debugoptimized
--werror
-Dtrim_dsp=false
- cd build && ninja
- meson test -v checkasm
test-ffmpeg:
extends: .debian-amd64-common
script:
- git clone --depth 1 --branch master https://code.ffmpeg.org/ffmpeg/ffmpeg
- cd ffmpeg
- rm -rf tests/checkasm/ext
- ln -s ../../.. tests/checkasm/ext
- ./configure --enable-gpl
- make -j$(getconf _NPROCESSORS_ONLN) run-checkasm
deploy-docs:
extends: .debian-amd64-common
stage: deploy
needs: ["build-docs"]
rules:
- if: '$CI_COMMIT_BRANCH == "master"'
script:
- mv build/docs/html public
pages:
publish: public

View File

@@ -1,172 +0,0 @@
# checkasm - for all your asm checking needs
**checkasm** is a tool for verifying the correctness of assembly code, as well as performance benchmarking.
## Usage
For a complete guide on getting started with checkasm, see the
[Getting Started](https://checkasm.videolan.me/getting_started.html) page.
```
Usage: checkasm [options...] <random seed>
<random seed> Use fixed value to seed the PRNG
Options:
--affinity=<cpu> Run the process on CPU <cpu>
--bench -b Benchmark the tested functions
--csv, --tsv, --json, Choose output format for benchmarks
--html
--function=<pattern> -f Test only the functions matching <pattern>
--help -h Print this usage info
--list-cpu-flags List available cpu flags
--list-functions List available functions
--list-tests List available tests
--duration=<μs> Benchmark duration (per function) in μs
--repeat[=<N>] Repeat tests N times, on successive seeds
--test=<pattern> -t Test only <pattern>
--verbose -v Print verbose timing info and failure data
```
## Supported platforms
The following architectures are explicitly supported for asm verification, with the listed detection mechanisms for typical asm mistakes:
- **x86, x86-64**: registers, stack, AVX2, MMX/FPU state
- **ARM, ARM64 (aarch64)**: registers, stack, VFP state
- **RISC-V**: registers, stack, vector state
- **LoongArch (32, 64)**: registers
- **PowerPC (64le)**: *none*
In addition, hardware timers are available for benchmarking purposes on all of the listed platforms (except RISCV-V), with fall-backs to generic OS-specific APIs.
## Integration into your project
You can either load checkasm as a library (e.g. via `pkg-config`), or include
it directly in your project's build system. See the
[Getting Started: Installation](https://checkasm.videolan.me/getting_started.html#installation)
and [Integration Guide](https://checkasm.videolan.me/integration.html) for more information.
### Code example
Here is what short example test demonstrating the public API. Check the
[Quick Start Example](https://checkasm.videolan.me/getting_started.html#quick_start)
for a full example.
```c
static void test_add8(const CheckasmCpu cpu)
{
#define WIDTH 1024
// Declare aligned buffers for testing
CHECKASM_ALIGN(uint8_t src1[WIDTH]);
CHECKASM_ALIGN(uint8_t src2[WIDTH]);
CHECKASM_ALIGN(uint16_t dst_c[WIDTH]);
CHECKASM_ALIGN(uint16_t dst_a[WIDTH]);
// Declare the function signature
checkasm_declare(void, uint16_t *, const uint8_t *, const uint8_t *, size_t);
if (checkasm_check_func(get_add8_func(cpu), "add_8")) {
// Initialize source buffers with quasi-random test vectors
INITIALIZE_BUF(src1);
INITIALIZE_BUF(src2);
// Test with various buffer sizes
for (int w = 1; w <= WIDTH; w <<= 1) {
// Clear destination buffers before each test
CLEAR_BUF(dst_c);
CLEAR_BUF(dst_a);
// Call reference and optimized implementations
checkasm_call_ref(dst_c, src1, src2, w);
checkasm_call_new(dst_a, src1, src2, w);
// Compare results - checkasm_check1d will report any mismatches
checkasm_check1d(uint16_t, dst_c, dst_a, w, "sum");
}
// Benchmark the optimized version on the largest buffer size
checkasm_bench_new(checkasm_alternate(dst_c, dst_a), src1, src2, WIDTH);
}
}
```
For a complete tutorial on writing tests, read the
[Writing Tests](https://checkasm.videolan.me/writing_tests.html) page.
### Example outputs
This is what the output looks like, when using `--verbose` mode to print all
timing data (on a pretty noisy/busy system). For more information about the
interpretation and usefulness of these results, refer to the
[Benchmarking](https://checkasm.videolan.me/benchmarking.html) guide.
```
checkasm:
- CPU: AMD Ryzen 9 9950X3D 16-Core Processor (00B40F40)
- Timing source: x86 (rdtsc)
- Timing overhead: 79.3 +/- 17.01 cycles per iteration
- Timing resolution: 0.2326 +/- 0.023 ns/cycle (4300 +/- 423.1 MHz)
- Bench duration: 2000 µs per function (8620136 cycles)
- Random seed: 3173025505
SSE2:
- msac.decode_symbol [OK]
- msac.decode_bool [OK]
- msac.decode_hi_tok [OK]
AVX2:
- msac.decode_symbol [OK]
checkasm: all 8 tests passed
Benchmark results:
name cycles +/- stddev time (nanoseconds) (vs ref)
msac_decode_bool_c: 7.3 +/- 0.5 2 ns +/- 0
msac_decode_bool_sse2: 5.4 +/- 0.6 1 ns +/- 0 ( 1.35x)
msac_decode_bool_adapt_c: 7.1 +/- 0.6 2 ns +/- 0
msac_decode_bool_adapt_sse2: 7.4 +/- 0.6 2 ns +/- 0 ( 0.97x)
msac_decode_bool_equi_c: 5.9 +/- 0.6 1 ns +/- 0
msac_decode_bool_equi_sse2: 4.0 +/- 0.6 1 ns +/- 0 ( 1.48x)
msac_decode_hi_tok_c: 92.9 +/- 19.7 22 ns +/- 5
msac_decode_hi_tok_sse2: 52.3 +/- 7.1 12 ns +/- 2 ( 1.78x)
msac_decode_symbol_adapt4_c: 19.3 +/- 1.2 4 ns +/- 1
msac_decode_symbol_adapt4_sse2: 12.8 +/- 0.6 3 ns +/- 0 ( 1.51x)
msac_decode_symbol_adapt8_c: 27.6 +/- 1.1 6 ns +/- 1
msac_decode_symbol_adapt8_sse2: 13.2 +/- 0.6 3 ns +/- 0 ( 2.10x)
msac_decode_symbol_adapt16_c: 44.1 +/- 1.2 10 ns +/- 1
msac_decode_symbol_adapt16_sse2: 14.6 +/- 0.6 3 ns +/- 0 ( 3.03x)
msac_decode_symbol_adapt16_avx2: 12.8 +/- 0.6 3 ns +/- 0 ( 3.45x)
```
And this is what a failure could look like:
```
checkasm:
- CPU: AMD Ryzen 9 9950X3D 16-Core Processor (00B40F40)
- Random seed: 3689286425
x86:
- x86.copy [OK]
FAILURE: sigill_x86 (illegal instruction)
- x86.sigill [FAILED]
FAILURE: corrupt_stack_x86 (stack corruption)
- x86.corrupt_stack [FAILED]
FAILURE: clobber_r9_x86 (failed to preserve register: r9)
FAILURE: clobber_r10_x86 (failed to preserve register: r10)
FAILURE: clobber_r11_x86 (failed to preserve register: r11)
FAILURE: clobber_r12_x86 (failed to preserve register: r12)
FAILURE: clobber_r13_x86 (failed to preserve register: r13)
FAILURE: clobber_r14_x86 (failed to preserve register: r14)
- x86.clobber [FAILED]
FAILURE: underwrite_64_x86 (../tests/generic.c:56)
dst data (64x1):
0: 45 b3 dc aa 68 90 a4 5d cf d9 d9 9b d0 34 45 b3 dc aa 68 90 a4 5d cf d9 d9 9b d0 34 ..............
1b 88 37 0f 14 f1 77 f4 94 bf 53 a3 21 3f 1b 88 37 0f 14 f1 77 f4 94 bf 53 a3 21 3f ..............
b5 64 77 fc 75 6b a9 2a 38 8a c7 e8 f9 1d b5 64 77 fc 75 6b a9 2a 38 8a c7 e8 f9 1d ..............
b4 80 d6 f4 34 1d c6 2b fd a8 e5 83 51 bb b4 80 d6 f4 34 1d c6 2b fd a8 e5 83 51 bb ..............
72 f6 e5 c1 47 80 63 f2 72 f6 e5 c1 aa aa aa aa ....xxxx
- generic.underwrite [FAILED]
```
## History and authors
This project was forked from [dav1d's](https://code.videolan.org/videolan/dav1d) internal copy of checkasm, which was itself a more-or-less up-to-date version of the various checkasm versions that existed in FFmpeg, x264 and so on.
This choice was made because dav1d was the closest to being feature complete, while also being permissively licensed and using a modern CI and build system. Some changes have been ported over from FFmpeg's copy of checkasm, with permission to relicense.
checkasm's original authors include Henrik Gramner, Loren Merritt, Fiona Glaser and others. This fork is maintained by Niklas Haas and Martin Storsjö.

View File

@@ -1,15 +0,0 @@
[binaries]
c = 'aarch64-linux-android21-clang'
cpp = 'aarch64-linux-android21-clang++'
ar = 'llvm-ar'
strip = 'llvm-strip'
pkgconfig = 'pkg-config'
[properties]
needs_exe_wrapper = true
[host_machine]
system = 'android'
cpu_family = 'aarch64'
cpu = 'aarch64'
endian = 'little'

View File

@@ -1,16 +0,0 @@
[binaries]
c = 'clang'
cpp = 'clang++'
ar = 'aarch64-linux-gnu-ar'
strip = 'aarch64-linux-gnu-strip'
exe_wrapper = 'qemu-aarch64'
[properties]
c_args = '-target aarch64-linux-gnu'
c_link_args = '-target aarch64-linux-gnu'
[host_machine]
system = 'linux'
cpu_family = 'aarch64'
cpu = 'aarch64'
endian = 'little'

View File

@@ -1,12 +0,0 @@
[binaries]
c = 'aarch64-linux-gnu-gcc'
cpp = 'aarch64-linux-gnu-g++'
ar = 'aarch64-linux-gnu-ar'
strip = 'aarch64-linux-gnu-strip'
exe_wrapper = 'qemu-aarch64'
[host_machine]
system = 'linux'
cpu_family = 'aarch64'
cpu = 'aarch64'
endian = 'little'

View File

@@ -1,16 +0,0 @@
[binaries]
c = 'aarch64-w64-mingw32-clang'
cpp = 'aarch64-w64-mingw32-clang++'
ar = 'aarch64-w64-mingw32-ar'
strip = 'aarch64-w64-mingw32-strip'
pkgconfig = 'pkg-config'
windres = 'aarch64-w64-mingw32-windres'
[properties]
c_link_args = ['-static-libgcc']
[host_machine]
system = 'windows'
cpu_family = 'aarch64'
cpu = 'aarch64'
endian = 'little'

View File

@@ -1,15 +0,0 @@
[binaries]
c = 'armv7a-linux-androideabi21-clang'
cpp = 'armv7a-linux-androideabi21-clang++'
ar = 'llvm-ar'
strip = 'llvm-strip'
pkgconfig = 'pkg-config'
[properties]
needs_exe_wrapper = true
[host_machine]
system = 'android'
cpu_family = 'arm'
cpu = 'arm'
endian = 'little'

View File

@@ -1,16 +0,0 @@
[binaries]
c = 'armv7-w64-mingw32-clang'
cpp = 'armv7-w64-mingw32-clang++'
ar = 'armv7-w64-mingw32-ar'
strip = 'armv7-w64-mingw32-strip'
pkgconfig = 'pkg-config'
windres = 'armv7-w64-mingw32-windres'
[properties]
c_link_args = ['-static-libgcc']
[host_machine]
system = 'windows'
cpu_family = 'arm'
cpu = 'armv7'
endian = 'little'

View File

@@ -1,15 +0,0 @@
[binaries]
c = 'gcc'
cpp = 'g++'
ar = 'ar'
strip = 'strip'
[host_machine]
system = 'linux'
cpu_family = 'x86'
cpu = 'i686'
endian = 'little'
[built-in options]
c_link_args = ['-m32', '-Wl,-z,text']
c_args = ['-m32']

View File

@@ -1,16 +0,0 @@
[binaries]
c = 'i686-w64-mingw32-gcc'
cpp = 'i686-w64-mingw32-g++'
ar = 'i686-w64-mingw32-ar'
strip = 'i686-w64-mingw32-strip'
windres = 'i686-w64-mingw32-windres'
exe_wrapper = 'wine'
[properties]
c_link_args = ['-static-libgcc']
[host_machine]
system = 'windows'
cpu_family = 'x86'
cpu = 'i686'
endian = 'little'

View File

@@ -1,13 +0,0 @@
[binaries]
c = 'loongarch64-unknown-linux-gnu-gcc'
cpp = 'loongarch64-unknown-linux-gnu-c++'
ar = 'loongarch64-unknown-linux-gnu-ar'
strip = 'loongarch64-unknown-linux-gnu-strip'
pkgconfig = 'pkg-config'
exe_wrapper = 'qemu-loongarch64'
[host_machine]
system = 'linux'
cpu_family = 'loongarch64'
cpu = 'loongarch64'
endian = 'little'

View File

@@ -1,16 +0,0 @@
[binaries]
c = 'clang'
cpp = 'clang++'
ar = 'riscv64-linux-gnu-ar'
strip = 'riscv64-linux-gnu-strip'
exe_wrapper = 'qemu-riscv64'
[properties]
c_args = '-target riscv64-linux-gnu'
c_link_args = '-target riscv64-linux-gnu'
[host_machine]
system = 'linux'
cpu_family = 'riscv64'
cpu = 'riscv64'
endian = 'little'

View File

@@ -1,12 +0,0 @@
[binaries]
c = 'riscv64-linux-gnu-gcc'
cpp = 'riscv64-linux-gnu-g++'
ar = 'riscv64-linux-gnu-ar'
strip = 'riscv64-linux-gnu-strip'
exe_wrapper = 'qemu-riscv64'
[host_machine]
system = 'linux'
cpu_family = 'riscv64'
cpu = 'riscv64'
endian = 'little'

View File

@@ -1,15 +0,0 @@
[binaries]
c = 'emcc'
cpp = 'em++'
ar = 'emar'
strip = 'emstrip'
exe_wrapper = 'node'
[properties]
c_link_args = ['-sEXPORT_ALL=1']
[host_machine]
system = 'emscripten'
cpu_family = 'wasm32'
cpu = 'wasm32'
endian = 'little'

View File

@@ -1,15 +0,0 @@
[binaries]
c = 'emcc'
cpp = 'em++'
ar = 'emar'
strip = 'emstrip'
exe_wrapper = 'node'
[properties]
c_link_args = ['-sEXPORT_ALL=1']
[host_machine]
system = 'emscripten'
cpu_family = 'wasm64'
cpu = 'wasm64'
endian = 'little'

View File

@@ -1,16 +0,0 @@
[binaries]
c = 'x86_64-w64-mingw32-gcc'
cpp = 'x86_64-w64-mingw32-g++'
ar = 'x86_64-w64-mingw32-ar'
strip = 'x86_64-w64-mingw32-strip'
windres = 'x86_64-w64-mingw32-windres'
exe_wrapper = 'wine'
[properties]
c_link_args = ['-static-libgcc']
[host_machine]
system = 'windows'
cpu_family = 'x86_64'
cpu = 'x86_64'
endian = 'little'

View File

@@ -1,20 +0,0 @@
# Project information
PROJECT_NAME = "checkasm"
PROJECT_BRIEF = "Assembly testing and benchmarking framework"
PROJECT_NUMBER = @VERSION@
# Input / output settings
INPUT = @INPUT@
LAYOUT_FILE = @LAYOUT_FILE@
EXCLUDE_SYMBOLS = internal
EXTRACT_STATIC = YES
MARKDOWN_SUPPORT = YES
OUTPUT_DIRECTORY = @BUILDDIR@
GENERATE_LATEX = NO
# Misc settings
OPTIMIZE_OUTPUT_FOR_C = YES
TYPEDEF_HIDES_STRUCT = YES
WARN_IF_UNDOCUMENTED = NO
STRIP_FROM_PATH = @INCDIR@

View File

@@ -1,49 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<doxygenlayout version="2.0">
<navindex>
<tab type="mainpage" visible="yes" title=""/>
<tab type="pages" visible="yes" title="" intro=""/>
<tab type="filelist" visible="yes" title="API Reference" intro=""/>
<tab type="examples" visible="yes" title="" intro=""/>
</navindex>
<!-- Layout definition for a file page -->
<file>
<briefdescription visible="no"/>
<includes visible="no"/>
<includegraph visible="no"/>
<includedbygraph visible="no"/>
<detaileddescription visible="yes" title=""/>
<sourcelink visible="yes"/>
<memberdecl>
<interfaces visible="yes" title=""/>
<classes visible="yes" title=""/>
<structs visible="yes" title=""/>
<exceptions visible="yes" title=""/>
<namespaces visible="yes" title=""/>
<concepts visible="yes" title=""/>
<constantgroups visible="yes" title=""/>
<defines visible="yes" title=""/>
<typedefs visible="yes" title=""/>
<sequences visible="yes" title=""/>
<dictionaries visible="yes" title=""/>
<enums visible="yes" title=""/>
<functions visible="yes" title=""/>
<variables visible="yes" title=""/>
<properties visible="yes" title=""/>
<membergroups visible="yes"/>
</memberdecl>
<memberdef>
<inlineclasses visible="yes" title=""/>
<defines visible="yes" title=""/>
<typedefs visible="yes" title=""/>
<sequences visible="yes" title=""/>
<dictionaries visible="yes" title=""/>
<enums visible="yes" title=""/>
<functions visible="yes" title=""/>
<variables visible="yes" title=""/>
<properties visible="yes" title=""/>
</memberdef>
<authorsection/>
</file>
</doxygenlayout>

View File

@@ -1,492 +0,0 @@
@page benchmarking Benchmarking
This guide explains how to use checkasm's benchmarking capabilities to accurately measure
and compare the performance of optimized assembly implementations against reference code.
@tableofcontents
@section bench_basic Basics
@subsection bench_workflow Benchmark Workflow
Benchmarking in checkasm follows the same structure as correctness testing, with
an additional call to checkasm_bench_new():
@code{.c}
BUF_RECT(uint8_t, src, 64, 64);
BUF_RECT(uint8_t, dst_c, 64, 64);
BUF_RECT(uint8_t, dst_a, 64, 64);
checkasm_declare(void, uint8_t *dst, ptrdiff_t dst_stride,
const uint8_t *src, ptrdiff_t src_stride,
int w, int h);
if (checkasm_check_func(dsp->filter, "filter_64x64")) {
INITIALIZE_BUF_RECT(src);
CLEAR_BUF_RECT(dst_c);
CLEAR_BUF_RECT(dst_a);
// Correctness testing
checkasm_call_ref(dst_c, dst_c_stride, src, src_stride, 64, 64);
checkasm_call_new(dst_a, dst_a_stride, src, src_stride, 64, 64);
checkasm_check_rect_padded(dst_c, dst_c_stride,
dst_a, dst_a_stride, 64, 64, "dst");
// Benchmarking
checkasm_bench_new(checkasm_alternate(dst_a, dst_c), dst_a_stride,
src, src_stride, 64, 64);
}
checkasm_report("filter");
@endcode
@subsection bench_cli Running Benchmarks
Enable benchmarking with the `--bench` flag:
@code{.bash}
# Quick benchmark (uses default duration)
./checkasm --bench
# Longer benchmark for more accurate results (10ms per function)
./checkasm --bench --duration=10000
# Benchmark specific functions only
./checkasm --bench --function='filter_*'
# Verbose output showing all timing measurements
./checkasm --bench --verbose
@endcode
The `--duration` parameter controls how long (in microseconds) each function is
benchmarked. Longer durations provide more accurate results but take more time.
The default is typically sufficient for most cases.
@subsection bench_export Exporting Results
checkasm can export benchmark results in multiple formats:
@code{.bash}
# CSV format (suitable for spreadsheets)
./checkasm --bench --csv > results.csv # without column headers
./checkasm --bench --csv --verbose > results.csv # with column headers
# JSON format (for programmatic analysis, includes all data)
./checkasm --bench --json > results.json
# HTML format (interactive visualizations)
./checkasm --bench --html > results.html
@endcode
The JSON output format includes all measurement data and detailed statistical
parameters, including kernel density estimates, regression parameters, and confidence
intervals. The HTML output displays this same data in the form of interactive charts.
@section bench_methodology Statistical Methodology
@subsection bench_lognormal Log-Normal Distribution Modeling
checkasm models execution time as a log-normal distribution, which is well-suited
for performance measurements because:
1. Execution time is always positive
2. Performance variations tend to be multiplicative (e.g. power states) rather than additive
3. Outliers (e.g., from cache misses or interrupts) naturally fall into the long tail
The statistical estimator tracks two parameters:
- **Log mean** (μ): the logarithm of the median execution time
- **Log variance** (σ²): the variance of log(execution time)
From these, checkasm computes:
- **Mode**: most likely execution time = exp(μ - σ²)
- **Median**: middle execution time = exp(μ)
- **Mean**: average execution time = exp(μ + σ²/2)
- **Standard deviation**: sqrt(exp(2μ + σ²) × (exp(σ²) - 1))
- **Upper/Lower 95% confidence intervals**: exp(μ ± 1.96 × σ)
@subsection bench_regression Linear Regression
checkasm performs linear regression in log-space on the relationship between
iteration count and total execution time:
@code{.plaintext}
log(per_call_time) = log(total_time) - log(iterations)
@endcode
This approach:
- Automatically handles the multiplicative nature of timing variations
- Provides robust outlier rejection through regression residuals
- Separates per-call time from measurement overhead
- Computes confidence intervals for the estimates
@subsection bench_geometric Geometric Mean for Multiple Runs
When checkasm_bench_new() is called multiple times for the same function
(e.g., testing different block sizes), the final reported value is the
**geometric mean** of all measurements:
@code{.plaintext}
geometric_mean = (x₁ × x₂ × ... × xₙ)^(1/n)
@endcode
The geometric mean is appropriate for performance measurements because it:
- Is not skewed by outliers as heavily as arithmetic mean
- Properly handles ratios and speedups across multiple orders of magnitude
- Provides a representative "typical" performance across configurations
@subsection bench_overhead Overhead Correction
checkasm measures and subtracts the overhead of:
1. The benchmarking loop itself
2. The function call mechanism
3. The timer read operation
This is done by measuring a no-op function and subtracting its measured time
from all benchmark results. The no-op overhead is reported at startup:
@code{.plaintext}
- No-op overhead: 2.41 +/- 0.093 cycles per call (provisional)
@endcode
For accuracy, this is re-estimated periodically throughout the benchmarking
process to account for any drift. The final value is reported again at the end
if `--verbose` is enabled.
@section bench_best_practices Best Practices
@subsection bp_system_state System State
Despite all of the statistical techniques employed by checkasm to combat
short- and medium-term noise, there is an unavoidable dependence on long-term
changes in system state. For reliable benchmarking, consider:
**Power Management:**
- Disable CPU frequency scaling if possible:
@code{.bash}
# Linux: set CPU governor to performance mode
sudo cpupower frequency-set --governor performance
# Or for all CPUs
for cpu in /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor; do
echo performance | sudo tee $cpu
done
@endcode
- Disable turbo boost for consistent results:
@code{.bash}
# Intel
echo 1 | sudo tee /sys/devices/system/cpu/intel_pstate/no_turbo
# AMD
echo 0 | sudo tee /sys/devices/system/cpu/cpufreq/boost
@endcode
**System Load:**
- Close unnecessary applications
- Avoid running benchmarks on heavily loaded systems
- Consider using `nice` to prioritize the benchmark process:
@code{.bash}
nice -n -20 ./checkasm --bench
@endcode
**Thermal Throttling:**
- Ensure adequate cooling to prevent thermal throttling
- Allow sufficient cool-down time between benchmark runs
- Monitor CPU temperature during long benchmark sessions
@subsection bp_alignment Cache Alignment
checkasm automatically warms up caches before taking measurements, but you
should be aware of cache effects:
- **L1/L2 Cache**: Most optimized functions should fit in L2 cache
- **Data Working Set**: Buffers allocated with BUF_RECT() are properly aligned
and sized to avoid cache conflicts. Failure to do so may lead to performance
hits from unaligned memory accesses or cache thrashing.
For functions with large working sets that don't fit in cache, benchmark results
may reflect cache miss behavior, which is often realistic for real-world usage.
@subsection bp_alternating Buffer Alternation
Use checkasm_alternate() when benchmarking to prevent cache pollution:
@code{.c}
// Good: alternates between dst_a and dst_c to prevent cache hits from
// previous iterations
checkasm_bench_new(checkasm_alternate(dst_a, dst_c), dst_a_stride,
src, src_stride, w, h);
// Acceptable: always writes to dst_a
checkasm_bench_new(dst_a, dst_a_stride, src, src_stride, w, h);
@endcode
Alternating buffers ensures that benchmarks are not stalled by previous
access to the same data buffer from the prior loop iteration.
@subsection bp_realistic Realistic Test Data
Use realistic input data for benchmarks:
@code{.c}
// For general data processing: use INITIALIZE_BUF() which includes common edge cases
INITIALIZE_BUF_RECT(src);
// For specific patterns: use domain-appropriate data
checkasm_randomize_normf(audio_buf, len); // Audio: normal distribution
// For worst-case analysis: test pathological inputs
generate_worst_case_pattern(buf, len);
@endcode
The input data can significantly affect performance due to:
- Data-dependent branches in the implementation
- SIMD instruction efficiency varying with data patterns
- Cache behavior depending on data values
If your function's performance varies significantly with the input data or
configuration, consider looping over all such configurations and running
checkasm_bench_new() for each, to measure an overall average.
@subsection bp_configurations Choosing Configurations
When benchmarking functions that support multiple sizes or configurations:
**Option 1: Benchmark all configurations**
@code{.c}
for (int w = 4; w <= 128; w <<= 1) {
if (checkasm_check_func(dsp->filter, "filter_w%d", w)) {
for (int h = 4; h <= 128; h <<= 1) {
// Test for correctness
// ...
// Benchmark each configuration
checkasm_bench_new(dst, dst_stride, src, src_stride, w, h);
}
// Reports geometric mean of all configurations
}
}
@endcode
**Option 2: Benchmark representative sizes separately**
@code{.c}
const int sizes[][2] = { {16, 16}, {16, 32}, {64, 16}, {64, 32} };
for (int i = 0; i < sizeof(sizes) / sizeof(sizes[0]); i++) {
int w = sizes[i][0], h = sizes[i][1];
// Separate check_func call = separate benchmark report
if (checkasm_check_func(dsp->filter, "filter_%dx%d", w, h)) {
// Test for correctness
// ...
checkasm_bench_new(dst, dst_stride, src, src_stride, w, h);
}
}
@endcode
**Option 3: Benchmark only the limiting case**
@code{.c}
for (int w = 4; w <= 128; w <<= 1) {
if (checkasm_check_func(dsp->filter, "filter_w%d", w)) {
for (int h = 4; h <= 128; h <<= 1) {
// Test all for correctness
// ...
}
// Benchmark only the largest size
checkasm_bench_new(dst, dst_stride, src, src_stride, w, 128);
}
}
@endcode
Choose based on your needs:
- Option 1: General performance across all sizes
- Option 2: Specific performance for important sizes
- Option 3: Best-case or worst-case performance
@section bench_interpreting Interpreting Results
@subsection interp_output Understanding Output
checkasm's benchmark output provides several pieces of information. Pass
`--verbose` to see all timing measurements.
@code{.plaintext}
Benchmark results:
name cycles +/- stddev time (nanoseconds) (vs ref)
nop: 2.4 +/- 0.0 1.2 ns +/- 0.1
filter_c: 64.9 +/- 75.4 33.8 ns +/- 40.8
filter_sse2: 46.9 +/- 13.3 24.4 ns +/- 7.8 ( 1.22x)
filter_avx2: 20.6 +/- 1.2 10.7 ns +/- 1.4 ( 3.15x)
@endcode
**Columns:**
- **name**: Function name (with ISA suffix for optimized versions)
- **cycles**: Estimated CPU cycles per call (mean ± standard deviation)
- **time**: Estimated nanoseconds per call (mean ± standard deviation)
- **(vs ref)**: Speedup relative to the reference (C) implementation
**What to look for:**
- Lower cycle counts indicate better performance
- Standard deviation shows measurement reliability
- Speedup factors show optimization effectiveness
@subsection interp_variance High Variance
High standard deviation (large ± values) can indicate:
1. **System noise**: Background processes, interrupts, frequency scaling
- Solution: Follow best practices in @ref bp_system_state
2. **Data-dependent performance**: Function runs faster/slower on different inputs
- This may be legitimate behavior (e.g., early exit conditions)
- Consider whether benchmark input is representative
3. **Cache effects**: Function doesn't fit in cache or has cache conflicts
- May be realistic for large working sets
- Ensure buffers are properly aligned
The benchmark summary reports average timing error:
@code{.plaintext}
- average timing error: 17.371% across 15 benchmarks (maximum 65.404%)
@endcode
High maximum error typically indicates at least one very noisy measurement.
@subsection interp_comparison Comparing Implementations
When comparing optimized implementations:
**Absolute speedup:**
@code{.plaintext}
filter_avx2: 20.6 cycles ( 3.15x)
@endcode
This implementation is 3.15× faster than the C reference.
**Relative comparison:**
@code{.plaintext}
filter_sse2: 46.9 cycles ( 1.22x)
filter_avx2: 20.6 cycles ( 3.15x)
@endcode
AVX2 is 46.9 / 20.6 = 2.28× faster than SSE2.
@subsection interp_regression Regression Detection
Use benchmark results to detect performance regressions:
1. **Baseline measurements**: Save benchmark results for your codebase:
@code{.bash}
./checkasm --bench --json > baseline.json
@endcode
2. **After changes**: Run benchmarks again:
@code{.bash}
./checkasm --bench --json > current.json
@endcode
3. **Compare**: Look for functions that got slower
- Small variations (< 5%) are typically noise
- Changes > 10% warrant investigation
- Changes > 20% are likely real regressions or improvements
@section bench_advanced Advanced Topics
@subsection adv_microbench Microbenchmarking Pitfalls
Be aware of common microbenchmarking issues:
- **Dead Code Elimination:** If the optimized function's results aren't used, the compiler might optimize
it away, especially when compiling with link time optimization. This would
usually be seen as unrealistically low cycle counts.
- **Constant Folding:** Always use INITIALIZE_BUF() or RANDOMIZE_BUF() to ensure inputs aren't
compile-time constants that could be folded away.
- **Branch Prediction:** Running the same code path repeatedly (as benchmarks do) leads to perfect
branch prediction, which may not reflect real-world performance that involve
mixed function calls (e.g. varying block sizes). This is generally acceptable
since you're comparing implementations under the same conditions, but may hide
performance gains from e.g. branchless implementations.
- **Memory Hierarchy:** Benchmarks often measure L1/L2 cache performance, not DRAM performance.
For functions with large working sets, real-world performance may be lower
than benchmarks suggest, which puts a bound on the realistically achievable
speedup from SIMD optimizations.
@subsection adv_platform Platform Considerations
@subsubsection adv_timer Timer Resolution
checkasm reports timer resolution at startup:
@code{.plaintext}
- Timing source: x86 (rdtsc)
- Timing resolution: 0.5976 +/- 0.057 ns/cycle (1644 +/- 156.8 MHz) (provisional)
@endcode
- x86/x86_64: rdtsc (cycle counter) - very high resolution
- ARM/AArch64: pmccntr (cycle counter) - high resolution
- LoongArch: rdtime (tick counter) - high resolution
- PowerPC 64le: mfspr (tick counter) - medium resolution
- Other/Fallback: OS-provided timers - lower resolution
Lower resolution timers may require longer `--duration` for accurate results.
@subsubsection adv_freq_scaling Frequency Scaling
The timer resolution includes clock frequency estimation. If CPU frequency
scaling is enabled, this estimate may be inaccurate. However, this affects
only the conversion to nanoseconds, not cycle counts. For most accurate results,
disable frequency scaling, or compare only raw cycle counts (for platforms with
access to high-resolution cycle counters).
@subsubsection adv_cross_platform Cross-Platform Comparison
Comparing cycle counts across different CPUs is meaningful when:
- Both CPUs are from the same architecture family
- Both run at similar clock speeds
- You account for microarchitectural differences
For cross-platform comparison, use relative speedup (optimized vs C) rather
than absolute cycle counts.
@subsection adv_html HTML Report Overview
The HTML report provides detailed statistical visualizations:
@subsubsection adv_kde_regression Kernel Density Estimate (left chart)
- Shows the probability distribution of execution times
- Peak indicates most likely execution time (mode)
- Wider distribution = higher variance
- Derived from log-normal distribution fit
@subsubsection adv_raw_measurements Raw Measurements (right chart)
- X-axis: iteration count (how many times function was called in one measurement)
- Y-axis: total time for all iterations in one measurement
- Line: linear regression fit
- Shaded area: 95% confidence interval
- Points far from line: potential outliers
@subsubsection adv_metrics Metrics Table
- **Adjusted cycles/time**: After overhead subtraction (use this for comparisons)
- **Raw cycles/time**: Before overhead subtraction (may be more reliable for sub-10-cycle functions)
@section bench_tips Tips and Tricks
@subsection tips_reproducible Reproducible Benchmarks
For reproducible results:
1. **Use fixed random seed**:
@code{.bash}
./checkasm --bench 12345 # Use seed 12345
@endcode
This ensures the same test data patterns across runs.
2. **Document system state**:
- CPU model and frequency settings
- Compiler version and flags
- Operating system and kernel version
- checkasm version/commit
3. **Multiple runs**:
Run benchmarks multiple times and verify consistency:
@code{.bash}
for i in {1..5}; do
./checkasm --bench --function='filter_*' | tee run_$i.txt
done
@endcode

View File

@@ -1,280 +0,0 @@
@page getting_started Getting Started
This guide will walk you through installing checkasm and writing your first test.
@tableofcontents
@section installation Installation
You can either load checkasm as a library (e.g. via `pkg-config`), or include it directly in your project's build system.
@subsection meson_submodules Meson using wrap files (recommended)
First, create `subprojects/checkasm.wrap`:
@code{.ini}
[wrap-git]
url = https://code.videolan.org/videolan/checkasm.git
revision = release # or a specific tag/release
directory = checkasm
@endcode
Then integrate it into your build system:
@code{.meson}
# This first attempts loading checkasm as an external dependency using the
# appropriate platform-specific method (e.g. pkg-config on POSIX systems),
# and falls back to using the bundled version inside `subprojects/checkasm`
# otherwise.
checkasm_dependency = dependency('checkasm',
# Extracts the `checkasm_dep` variable from the `checkasm` subproject.
fallback: ['checkasm', 'checkasm_dep'],
required: false
)
# Alternatively, you can directly force use of the bundled version:
# checkasm_dependency = subproject('checkasm').get_variable('checkasm_dep')
if checkasm_dependency.found()
checkasm = executable('checkasm',
checkasm_sources,
dependencies: checkasm_dependency,
)
test('checkasm', checkasm, suite: 'checkasm')
benchmark('checkasm', checkasm, suite: 'checkasm', args: '--bench')
endif
@endcode
@subsection meson_wrap Meson using submodules (alternative)
As an alternative, you may use git submodules to include checkasm as a subproject.
This may be preferred in some environments where the build system cannot access
the internet during configuration time, or if you're already using submodules
in your project.
@code{.bash}
git submodule init
git submodule add -b release https://code.videolan.org/videolan/checkasm subprojects/checkasm
# or checkout a specific tag/release
@endcode
Then declare the dependency in your `meson.build` as usual. (See the previous section)
@subsection manual_installation Manual Installation
You can also build and install checkasm manually:
@code{.bash}
git clone https://github.com/videolan/checkasm.git && cd checkasm
meson setup builddir -Dprefix=$PREFIX # (set optional build prefix)
meson compile -C builddir
meson install -C builddir
@endcode
This is discouraged in favor of using Meson subprojects or distribution packages,
but may be useful inside containerized environments, CI systems or custom
build roots.
@section quick_start Quick Start Example
Let's create a simple test for a vector addition function that operates on buffers.
@subsection quick_start1 1. Prerequisites
Let's assume you have a reference implementation and an optimized version,
alongside a way of detecting CPU features and choosing the implementation
based on that:
@code{.h}
// my_dsp.h
#include <stdint.h>
enum {
CPU_FLAG_AVX = 1 << 0,
};
unsigned detect_cpu_flags(void);
typedef void (*add8_func_t)(uint16_t *dst, const uint8_t *src1,
const uint8_t *src2, size_t len);
add8_func_t get_add8_func(unsigned cpu_flags);
@endcode
@code{.c}
// my_dsp.c
#include "my_cpu.h"
// Reference implementation (pure C)
static void add8_c(uint16_t *dst, const uint8_t *src1,
const uint8_t *src2, size_t len)
{
for (size_t i = 0; i < len; i++)
dst[i] = src1[i] + src2[i];
}
// Optimized implementation (pretend this is assembly)
static void add8_avx(uint16_t *dst, const uint8_t *src1,
const uint8_t *src2, size_t len)
{
// Assembly optimized version would go here
add8_c(dst, src1, src2, len);
}
add8_func_t get_add8_func(unsigned cpu_flags)
{
if (cpu_flags & CPU_FLAG_AVX)
return add8_avx;
return add8_c;
}
@endcode
@subsection quick_start2 2. Write the Test
Create your test file:
@code{.c}
// check_dsp.c
#include <checkasm/checkasm.h>
#include <checkasm/test.h>
#include "my_dsp.h"
#define WIDTH 1024
static void test_add8(const CheckasmCpu cpu)
{
// Declare aligned buffers for testing
CHECKASM_ALIGN(uint8_t src1[WIDTH]);
CHECKASM_ALIGN(uint8_t src2[WIDTH]);
CHECKASM_ALIGN(uint16_t dst_c[WIDTH]);
CHECKASM_ALIGN(uint16_t dst_a[WIDTH]);
// Declare the function signature
checkasm_declare(void, uint16_t *, const uint8_t *, const uint8_t *, size_t);
if (checkasm_check_func(get_add8_func(cpu), "add_8")) {
// Initialize source buffers with quasi-random test vectors
INITIALIZE_BUF(src1);
INITIALIZE_BUF(src2);
// Test with various buffer sizes
for (int w = 1; w <= WIDTH; w <<= 1) {
// Clear destination buffers before each test
CLEAR_BUF(dst_c);
CLEAR_BUF(dst_a);
// Call reference and optimized implementations
checkasm_call_ref(dst_c, src1, src2, w);
checkasm_call_new(dst_a, src1, src2, w);
// Compare results - checkasm_check1d will report any mismatches
checkasm_check1d(uint16_t, dst_c, dst_a, w, "sum");
}
// Benchmark the optimized version on the largest buffer size
checkasm_bench_new(checkasm_alternate(dst_c, dst_a), src1, src2, WIDTH);
}
}
static void check_dsp(void)
{
const CheckasmCpu cpu = checkasm_get_cpu_flags();
// Test all related functions and report as a single function group
test_add8(cpu);
// test_add16(cpu);
// ...
checkasm_report("add");
// Check more function groups
// ...
}
// Test registry
static const CheckasmTest tests[] = {
{ "dsp", check_dsp },
{0} // array terminator
};
// CPU flag registry
static const CheckasmCpuInfo cpu_flags[] = {
{ "AVX", "avx", CPU_FLAG_AVX },
{0} // array terminator
};
int main(int argc, const char *argv[]) {
CheckasmConfig config = {
.tests = tests,
.cpu_flags = cpu_flags,
.cpu = detect_cpu_flags(),
};
return checkasm_main(&config, argc, argv);
}
@endcode
@subsection quick_start3 3. Build and Run
@code{.bash}
# Compile (example using gcc directly)
gcc -o check_dsp my_dsp.c check_dsp.c $(pkg-config --cflags --libs checkasm)
# or use `meson compile` if using Meson
# Run all tests
./check_dsp
@endcode
@section options Command-Line Options
checkasm provides several useful command-line options:
@code{.bash}
# List all available functions
./checkasm --list-functions
# Run specific functions (supports wildcards)
./checkasm --function=add_*_8bpc
# Run benchmarks
./checkasm --bench
# Run specified test with higher benchmark duration (here: 10 ms)
./checkasm --test=pixel --bench --duration=10000
# Enable verbose output
./checkasm --verbose
@endcode
The `--help` output shows all available options:
@code{.txt}
Usage: checkasm [options...] <random seed>
<random seed> Use fixed value to seed the PRNG
Options:
--affinity=<cpu> Run the process on CPU <cpu>
--bench -b Benchmark the tested functions
--csv, --tsv, --json, Choose output format for benchmarks
--html
--function=<pattern> -f Test only the functions matching <pattern>
--help -h Print this usage info
--list-cpu-flags List available cpu flags
--list-functions List available functions
--list-tests List available tests
--duration=<μs> Benchmark duration (per function) in μs
--repeat[=<N>] Repeat tests N times, on successive seeds
--test=<pattern> -t Test only <pattern>
--verbose -v Print verbose timing info and failure data
@endcode
---
@section getting_started_next_steps Next Steps
Now that you've set up checkasm and written your first test, learn how to
integrate it properly with your project's CPU detection and dispatch mechanisms.
**Next:** @ref integration

View File

@@ -1,274 +0,0 @@
@page integration Integration Guide
This guide covers how to integrate checkasm into existing or new projects.
@tableofcontents
@section config_options Configuration Options
Checkasm uses optional C11 features inside public header files. For safety, these are only enabled
conservatively based on the C11 standard version signaled by the compiler. Sometimes, these checks
could be relaxed, such as when the target project is explicitly compiled with `-std=c99` or older,
but using a modern compiler that would still understand and accept C11 features. In this case,
these feature checks may be checked by the user and defined before including checkasm.h. See
@ref config for a list of such options.
@section cpu_flags CPU Flags
CPU flags represent instruction set extensions and features that your optimized implementations
depend on (e.g., SSE2, AVX2, NEON). You must define an array of these flags, so checkasm can
systematically test each implementation variant.
@note checkasm does not provide CPU detection or runtime dispatch functionality on its own.
It is a pure testing framework, and as such, should not be used as a runtime dependency of your
project. This means that your project must implement its own CPU feature detection and dispatch
mechanisms for production use. checkasm plugs into these existing mechanisms during testing.
@subsection defining_cpu_flags Defining CPU Flags
Assuming you have a set of CPU flags defined in your project, e.g.,
@code{.h}
// my_cpu.h
enum {
CPU_FLAG_SSE2 = 1 << 0,
CPU_FLAG_SSSE3 = 1 << 1,
CPU_FLAG_SSE41 = 1 << 2,
CPU_FLAG_AVX2 = 1 << 3,
CPU_FLAG_AVX512 = 1 << 4,
// ...
};
typedef uint64_t MyCpuFlags;
MyCpuFlags detect_cpu_flags(void);
@endcode
Then create a CheckasmCpuInfo array describing each flag, terminated by `{0}`:
@code{.c}
// checkasm.c
static const CheckasmCpuInfo cpu_flags[] = {
{ "SSE2", "sse2", CPU_FLAG_SSE2 },
{ "SSSE3", "ssse3", CPU_FLAG_SSSE3 },
{ "SSE4.1", "sse41", CPU_FLAG_SSE41 },
{ "AVX2", "avx2", CPU_FLAG_AVX2 },
{ "AVX512", "avx512", CPU_FLAG_AVX512 },
{0} // array terminator
};
// This ordering means:
// - SSE2 functions are tested with just CPU_FLAG_SSE2
// - SSSE3 functions are tested with CPU_FLAG_SSE2 | CPU_FLAG_SSSE3
// - SSE4.1 functions are tested with CPU_FLAG_SSE2 | CPU_FLAG_SSSE3 | CPU_FLAG_SSE41
// - And so on...
@endcode
Each entry contains:
- **name**: Human-readable name displayed in output (e.g., "SSE4.1")
- **suffix**: Short suffix used in function names and filtering (e.g., "sse41")
- **flag**: The bitfield value from your CPU flag enum
@note Flags are tested in the order defined in the array. Each test inherits
flags from all previous entries, allowing checkasm to test progressively more
advanced instruction sets.
Register the CPU flags with checkasm via the CheckasmConfig structure:
@code{.c}
// checkasm.c
static const CheckasmCpuInfo cpu_flags[] = {
// ...
{0}
};
int main(int argc, const char *argv[])
{
CheckasmConfig config = {
.cpu_flags = cpu_flags,
// ...
};
// Set initial CPU flags using your own runtime detection function
config.cpu = detect_cpu_flags();
return checkasm_main(&config, argc, argv);
}
@endcode
@subsection extra_cpu_flags Extra CPU Flags
You can include additional flags in CheckasmConfig.cpu that aren't in CheckasmConfig.cpu_flags.
These are transparently passed through to checkasm_get_cpu_flags() and can be used for
modifier flags like `CPU_FLAG_FAST_*` that don't require separate testing, but should
instead always be assumed to be available when matching function implementations.
@section selecting_functions Selecting Functions
There are two common strategies for selecting the correct function implementation during tests,
depending on how your project structures its dispatch mechanism:
@note Choose the strategy that matches your project's existing architecture. If you are
developing a new library, we recommend the first approach.
@subsection mask_callback Strategy 1: Mask Callback
If your project has a `mask_cpu_flags` (or `cpu_flags_override`) function that
updates an internal static bitmask used internally by dispatch table getters, e.g.:
@code{.c}
// my_cpu.c
static unsigned cpu_flags = 0;
static unsigned cpu_flags_mask = -1;
unsigned get_cpu_flags(void)
{
return cpu_flags & cpu_flags_mask;
}
void mask_cpu_flags(unsigned flags)
{
cpu_flags_mask = flags;
}
@endcode
@code{.c}
// my_foo_dsp.c
void foo_dsp_init(foo_dsp *dsp)
{
const unsigned cpu_flags = get_cpu_flags();
// Initialize with C implementations
dsp->add = add_c;
dsp->sub = sub_c;
// Override with optimized versions based on cpu_flags
if (cpu_flags & CPU_FLAG_SSE2) {
dsp->add = add_sse2;
}
if (cpu_flags & CPU_FLAG_AVX2) {
dsp->add = add_avx2;
dsp->sub = sub_avx2;
}
// ...
}
@endcode
Then, in your checkasm main file, you can set that directly as a callback in CheckasmConfig.set_cpu_flags:
@code{.c}
// You may need a wrapper to fix the function signature
static void set_cpu_flags(CheckasmCpu cpu)
{
mask_cpu_flags((unsigned) cpu);
}
CheckasmConfig config = {
// ...
.set_cpu_flags = set_cpu_flags,
};
@endcode
With this approach, checkasm will automatically call your `set_cpu_flags()` function whenever
it changes the active CPU feature set during testing.
@note This will always be a subset of the initially detected CPU flags provided in CheckasmConfig.cpu,
so there is no meaningful distinction between a `mask_cpu_flags` (that masks out real CPU
flags) and a `cpu_flags_override` (that overrides them wholesale). Both can be used
as a callback.
@subsection direct_getters Strategy 2: Direct Getters
If your project uses dispatch functions that directly accept a CPU mask parameter (e.g.,
`void foo_dsp_init(foo_dsp *dsp, unsigned cpu_flags)`), you can call them within
each test using checkasm_get_cpu_flags():
@code{.c}
// my_foo_dsp.c
void foo_dsp_init(foo_dsp *dsp, unsigned cpu_flags)
{
// Initialize *dsp based on the provided CPU flags
}
@endcode
Then, in your checkasm test files:
@code{.c}
// check_foo_dsp.c
void check_foo_dsp(void)
{
foo_dsp dsp;
foo_dsp_init(&dsp, checkasm_get_cpu_flags()); // Get current test flags
// Now test dsp.add, dsp.sub, etc.
// ...
}
@endcode
The same applies if your project uses individual function getters like
`add_func_t get_add_func(unsigned cpu_flags)` instead of dispatch tables / dsp structs.
@section organizing_tests Organizing Multiple Tests
For larger projects, organize tests by module:
@code{.c}
// Test module declarations
void checkasm_check_mc(void);
void checkasm_check_pixel(void);
void checkasm_check_filmgrain(void);
// ...
static const CheckasmTest tests[] = {
{ "mc", checkasm_check_mc },
{ "pixel", checkasm_check_pixel },
{ "filmgrain", checkasm_check_filmgrain },
// ...
{0}
};
@endcode
Then implement each test in separate files:
@code{.c}
// check_mc.c
#include <checkasm/test.h>
#include "mc_dsp.h"
static void test_mc_func1(const mc_dsp *dsp);
static void test_mc_func2(const mc_dsp *dsp);
static void test_mc_func3(const mc_dsp *dsp);
// ...
void checkasm_check_mc(void)
{
mc_dsp dsp;
mc_dsp_init(&dsp, checkasm_get_cpu_flags());
test_func1(&dsp);
test_func2(&dsp);
test_func3(&dsp);
checkasm_report("group1");
test_func4(&dsp);
test_func5(&dsp);
test_func6(&dsp);
checkasm_report("group2");
// ...
}
@endcode
---
@section integration_next_steps Next Steps
Now that you know how to integrate checkasm with your project's architecture,
dive deeper into best practices and advanced patterns for writing comprehensive
tests.
**Next:** @ref writing_tests

View File

@@ -1,148 +0,0 @@
@mainpage Introduction
**checkasm** is a robust, portable testing and benchmarking framework specifically designed for
validating optimized assembly implementations against reference C code. Originally developed for the
x264 encoder project, checkasm has grown considerably while in use by FFmpeg and dav1d,
and now provides comprehensive correctness verification, crash detection,
and accurate performance measurements across multiple architectures.
This project in particular stems from an effort to combine and improve upon multiple diverging
checkasm forks in the wild (x264, FFmpeg, dav1d, etc.). It is a direct descendent of the dav1d
variant, with all relevant enhancements from the FFmpeg fork merged in. It has since evolved
into a fully fledged cross-platform standalone library and benchmarking framework.
@tableofcontents
@section features Key Features
@subsection features_correctness Correctness Verification
- **Automated comparison** between reference (C) and optimized (assembly) implementations, including
support for fuzzing implementations over multiple seeds and data patterns.
- **Detection of ABI violations** such as register misuse, illegal opcodes,
or missing state cleanup.
- **Extensive data handling utilities** for defining, initializing, and comparing
input/output buffers of various types, sizes and dimensionalities.
- **Crash resilience** including signal handling and stack smashing detection.
@subsection features_benchmarking Performance Benchmarking
- **Accurate cycle counting** using platform-specific high-resolution timers.
- **Statistical analysis** with robust outlier rejection, logarithmic regression and confidence intervals.
- **Comparative measurements** showing speedup of optimized code including comprehensive HTML reports.
- **Minimal overhead** to ensure realistic performance data, and high throughput.
@subsection features_platform Platform Support
The following architectures are explicitly supported for asm verification, with
the listed detection mechanisms for typical asm mistakes:
- **x86, x86-64**: registers, stack, AVX2, MMX/FPU state
- **ARM, ARM64 (aarch64)**: registers, stack, VFP state
- **RISC-V**: registers, stack, vector state
- **LoongArch (32, 64)**: registers
- **PowerPC (64le)**: *none*
In addition, hardware timers are available for benchmarking purposes on all of
the listed platforms (except RISCV-V), with fall-backs to generic OS-specific APIs.
@section links Project Links
- [**GitLab Repository**](https://code.videolan.org/videolan/checkasm)
- [**Issue Tracker**](https://code.videolan.org/videolan/checkasm/issues)
- [VideoLAN Homepage](https://www.videolan.org/)
@subsection intro_next_steps Next Steps
Continue learning about checkasm through the following guides:
- @ref getting_started for a quick start guide
- @ref integration for a guide on how to integrate checkasm into your project
- @ref writing_tests for an in-depth explanation of writing tests
- @ref benchmarking for tips and tricks on benchmarking with checkasm
Or explore the full API reference:
- @ref checkasm.h -- @copybrief checkasm.h
- @ref test.h -- @copybrief test.h
- @ref utils.h -- @copybrief utils.h
@section example_outputs Example Outputs
@subsection example_benchmark Benchmark Output
This is what the output looks like, when using `--verbose` mode to print all
timing data (on a pretty noisy/busy system):
@code{.plaintext}
checkasm:
- CPU: Intel(R) Core(TM) Ultra 7 258V (000B06D1)
- Timing source: x86 (rdtsc)
- Timing resolution: 0.5976 +/- 0.057 ns/cycle (1644 +/- 156.8 MHz) (provisional)
- No-op overhead: 2.41 +/- 0.093 cycles per call (provisional)
- Bench duration: 10000 µs per function (18221161 cycles)
- Random seed: 3203178780
C:
- msac.decode_symbol [OK]
- msac.decode_bool [OK]
- msac.decode_hi_tok [OK]
SSE2:
- msac.decode_symbol [OK]
- msac.decode_bool [OK]
- msac.decode_hi_tok [OK]
AVX2:
- msac.decode_symbol [OK]
checkasm: all 15 tests passed
Benchmark results:
name cycles +/- stddev time (nanoseconds) (vs ref)
nop: 2.4 +/- 0.0 1.2 ns +/- 0.1
msac_decode_bool_c: 6.3 +/- 0.2 3.3 ns +/- 0.4
msac_decode_bool_sse2: 5.7 +/- 0.2 3.0 ns +/- 0.4 ( 1.10x)
msac_decode_bool_adapt_c: 5.9 +/- 0.6 3.1 ns +/- 0.5
msac_decode_bool_adapt_sse2: 6.3 +/- 0.2 3.3 ns +/- 0.4 ( 0.94x)
msac_decode_bool_equi_c: 5.1 +/- 0.2 2.6 ns +/- 0.3
msac_decode_bool_equi_sse2: 2.8 +/- 0.1 1.4 ns +/- 0.2 ( 1.84x)
msac_decode_hi_tok_c: 64.9 +/- 75.4 33.8 ns +/- 40.8
msac_decode_hi_tok_sse2: 46.9 +/- 13.3 24.4 ns +/- 7.8 ( 1.22x)
msac_decode_symbol_adapt4_c: 12.6 +/- 0.7 6.5 ns +/- 0.9
msac_decode_symbol_adapt4_sse2: 12.3 +/- 0.8 6.4 ns +/- 0.9 ( 1.02x)
msac_decode_symbol_adapt8_c: 20.5 +/- 2.3 10.7 ns +/- 1.8
msac_decode_symbol_adapt8_sse2: 12.1 +/- 0.8 6.3 ns +/- 0.8 ( 1.68x)
msac_decode_symbol_adapt16_c: 33.6 +/- 1.2 17.5 ns +/- 2.1
msac_decode_symbol_adapt16_sse2: 13.8 +/- 0.6 7.2 ns +/- 0.9 ( 2.42x)
msac_decode_symbol_adapt16_avx2: 20.6 +/- 1.2 10.7 ns +/- 1.4 ( 1.62x)
- average timing error: 17.371% across 15 benchmarks (maximum 65.404%)
@endcode
@subsection example_errors Error Reporting
This is what various failures could look like:
@code{.plaintext}
checkasm:
- CPU: AMD Ryzen 9 9950X3D 16-Core Processor (00B40F40)
- Random seed: 3689286425
x86:
- x86.copy [OK]
FAILURE: sigill_x86 (illegal instruction)
- x86.sigill [FAILED]
FAILURE: corrupt_stack_x86 (stack corruption)
- x86.corrupt_stack [FAILED]
FAILURE: clobber_r9_x86 (failed to preserve register: r9)
FAILURE: clobber_r10_x86 (failed to preserve register: r10)
FAILURE: clobber_r11_x86 (failed to preserve register: r11)
FAILURE: clobber_r12_x86 (failed to preserve register: r12)
FAILURE: clobber_r13_x86 (failed to preserve register: r13)
FAILURE: clobber_r14_x86 (failed to preserve register: r14)
- x86.clobber [FAILED]
FAILURE: underwrite_64_x86 (../tests/generic.c:56)
dst data (64x1):
0: 45 b3 dc aa 68 90 a4 5d cf d9 d9 9b d0 34 45 b3 dc aa 68 90 a4 5d cf d9 d9 9b d0 34 ..............
1b 88 37 0f 14 f1 77 f4 94 bf 53 a3 21 3f 1b 88 37 0f 14 f1 77 f4 94 bf 53 a3 21 3f ..............
b5 64 77 fc 75 6b a9 2a 38 8a c7 e8 f9 1d b5 64 77 fc 75 6b a9 2a 38 8a c7 e8 f9 1d ..............
b4 80 d6 f4 34 1d c6 2b fd a8 e5 83 51 bb b4 80 d6 f4 34 1d c6 2b fd a8 e5 83 51 bb ..............
72 f6 e5 c1 47 80 63 f2 72 f6 e5 c1 aa aa aa aa ....xxxx
- generic.underwrite [FAILED]
@endcode
@section license License
checkasm is licensed under the **2-clause BSD license**. See the LICENSE file in the repository for full details.

View File

@@ -1,77 +0,0 @@
# Copyright © 2025 Niklas Haas
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
doxygen = find_program('doxygen', required: build_docs)
if not doxygen.found()
subdir_done()
endif
doxygen_file_names = [
'introduction.md',
'getting-started.md',
'integration.md',
'writing-tests.md',
'benchmarking.md',
]
doxygen_paths = checkasm_api_header_paths
foreach file_name : doxygen_file_names
doxygen_paths += meson.current_source_dir() / file_name
endforeach
# Escape input files for "INPUT" option in Doxyfile
doxygen_inputs = []
foreach path : doxygen_paths
path = path.replace('\\', '\\\\')
path = path.replace('"', '\\"')
doxygen_inputs += ['"' + path + '"']
endforeach
docs_conf = configuration_data()
docs_conf.set('INPUT', ' '.join(doxygen_inputs))
docs_conf.set('VERSION', meson.project_version())
docs_conf.set_quoted('BUILDDIR', meson.current_build_dir())
docs_conf.set_quoted('INCDIR', checkasm_include_dir)
doxygen_layout = meson.current_source_dir() / 'DoxygenLayout.xml'
docs_conf.set_quoted('LAYOUT_FILE', doxygen_layout)
doxyfile = configure_file(
input: 'Doxyfile.in',
output: 'Doxyfile',
configuration: docs_conf,
install: false
)
custom_target('docs',
input: doxyfile,
output: 'html',
command: [doxygen, '@INPUT@'],
depend_files: files(doxygen_paths, doxygen_layout),
build_by_default: false,
install: false,
console: true,
)
message('Run "meson compile docs" to build HTML documentation')

View File

@@ -1,753 +0,0 @@
@page writing_tests Writing Tests
This guide covers best practices and common patterns for writing checkasm tests.
@tableofcontents
@section test_structure Basic Test Structure
Before diving into advanced patterns and best practices, familiarize yourself
with the basic test structure by reading the @ref quick_start.
The typical test workflow is:
1. Allocate (aligned) buffers for test data
2. Declare the function signature with checkasm_declare()
3. Check if the function should be tested with checkasm_check_func()
4. Initialize test inputs and clear output buffers
5. Call both reference (checkasm_call_ref()) and new (checkasm_call_new()) implementations
6. Compare results with checkasm_check2d() or similar
7. Benchmark the new implementation with checkasm_bench_new()
8. Report results with checkasm_report() (optional)
@section naming_conventions API Naming Conventions
checkasm supports two API naming styles:
@subsection modern_api Modern API (Recommended)
The modern API uses the `checkasm_` prefix for all functions:
@code{.c}
checkasm_declare(void, uint8_t *dst, const uint8_t *src, int len);
checkasm_check_func(dsp->func, "func_name")
checkasm_call_ref(dst_c, src, len);
checkasm_call_new(dst_a, src, len);
checkasm_check1d(uint8_t, dst_c, dst_a, len, "dst");
checkasm_bench_new(dst_a, src, len);
checkasm_report("func_name");
checkasm_fail()
checkasm_alternate(buf0, buf1)
// ...
@endcode
@subsection legacy_api Legacy/Short API
For convenience and backwards compatibility, shorter aliases are available:
@code{.c}
declare_func(void, uint8_t *dst, const uint8_t *src, int len);
check_func(dsp->func, "func_name")
call_ref(dst_c, src, len);
call_new(dst_a, src, len);
// checkasm_check1d() has no short alias
bench_new(dst_a, src, len);
report("func_name");
fail()
alternate(buf0, buf1)
@endcode
@note Both naming styles are fully supported and can be mixed within the same
test file. However, for consistency and readability in documentation and new
code, we recommend using the modern `checkasm_` prefixed names.
@section best_practices Best Practices
The remainder of this guide focuses on best practices, common patterns, and advanced testing scenarios.
@subsection bp_buffer_allocation Buffer Allocation
Always use properly aligned buffers for testing:
@code{.c}
// For simple arrays
CHECKASM_ALIGN(uint8_t buf[1024]);
// For 2D buffers with automatic padding and stride calculation
// - Defines `dst` as a pointer to a 64x32 area, and `dst_stride` (in bytes)
BUF_RECT(uint8_t, dst, 64, 32);
@endcode
**Important:** Apply CHECKASM_ALIGN() to each buffer individually:
@code{.c}
// Correct
CHECKASM_ALIGN(uint8_t buf1[32]);
CHECKASM_ALIGN(uint8_t buf2[32]);
// Wrong - only buf1 will be aligned
CHECKASM_ALIGN(uint8_t buf1[32], buf2[32]);
@endcode
@subsection bp_buffer_init Buffer Initialization
checkasm provides several buffer initialization functions:
@code{.c}
// Random data (uniformly distributed)
RANDOMIZE_BUF(buf);
checkasm_randomize(buf, sizeof(buf)); // Equivalent to the above macro
checkasm_randomize_mask8 (buf8, width, 0x0F); // Constrain value range
checkasm_randomize_mask16(buf16, width, 1023); // Random 10-bit values
// Pathological test patterns (mix of edge cases and random bytes)
INITIALIZE_BUF(buf);
checkasm_init(buf, sizeof(buf)); // Equivalent to the above macro
checkasm_init_mask16(buf16, width, (1 << 12) - 1); // Constrain to 12-bit range
// Clear to a constant value
CLEAR_BUF(buf);
checkasm_clear(buf, sizeof(buf)); // Equivalent to the above macro
checkasm_clear16(buf16, width, 0x1234);
@endcode
For 2D buffers created with BUF_RECT():
@code{.c}
BUF_RECT(uint8_t, src, 64, 32);
INITIALIZE_BUF_RECT(src); // Initialize with pathological test bytes
RANDOMIZE_BUF_RECT(tmp); // Fill with random data
CLEAR_BUF_RECT(dst); // Clear to constant data (currently 0xAA)
@endcode
See @ref memory for a full list of buffer initialization functions.
@subsection bp_test_loops Testing Multiple Configurations
Test functions across multiple configurations (e.g. block sizes) to ensure
comprehensive coverage:
@code{.c}
BUF_RECT(uint8_t, src, 128, 128);
BUF_RECT(uint8_t, dst_c, 128, 128);
BUF_RECT(uint8_t, dst_a, 128, 128);
checkasm_declare(void, uint8_t *dst, ptrdiff_t dst_stride,
const uint8_t *src, ptrdiff_t src_stride,
int w, int h);
// Test various power-of-two block sizes, from 4x4 to 128x128
for (int h = 4; h <= 128; h <<= 1) {
for (int w = 4; w <= 128; w <<= 1) {
if (checkasm_check_func(get_func(w), "func_%dx%d", w, h)) {
// Initialize test data - doing this inside the loop picks a
// different data pattern for each test
INITIALIZE_BUF(src);
CLEAR_BUF(dst_c);
CLEAR_BUF(dst_a);
// Test this configuration
checkasm_call_ref(dst_c, dst_c_stride, src, src_stride, w, h);
checkasm_call_new(dst_a, dst_a_stride, src, src_stride, w, h);
checkasm_check_rect_padded(dst_c, dst_c_stride,
dst_a, dst_a_stride, w, h, "dst");
// Benchmark this configuration
checkasm_bench_new(checkasm_alternate(dst_a, dst_c), dst_a_stride,
src, src_stride, w, h);
}
}
}
checkasm_report("func");
@endcode
@subsection bp_random_params Using Random Parameters
Use checkasm_rand() and related functions to generate diverse test inputs:
@code{.c}
// Random integers
int byte = checkasm_rand() & 0xFF;
uint32_t flags = checkasm_rand_uint32();
// Random floats
double d = checkasm_randf(); // [0.0, 1.0)
float f = (float) checkasm_randf() * 100.0f; // [0.0, 100.0)
// Normal distribution
double normal = checkasm_rand_norm(); // mean=0, stddev=1
checkasm_randomize_normf(buf, len); // Fill buffer with N(0,1)
@endcode
See @ref rng for a full list of random number generation functions.
@subsection bp_reporting Organizing Reports
Group related functions and use checkasm_report() strategically:
@code{.c}
static void check_add_functions(const DSPContext *dsp)
{
// Test multiple related functions
for (int bpc = 8; bpc <= 12; bpc += 2) {
if (checkasm_check_func(get_add8(bpc), "add8_%dbpc", bpc)) {
// Test add8
}
if (checkasm_check_func(get_add16(bpc), "add16_%dbpc", bpc)) {
// Test add16
}
}
// Report once for the whole group
checkasm_report("add");
}
@endcode
For very simple tests with only a few functions, for which there is no
logical grouping, or for miscellaneous functions, this can be left out.
Any functions without a checkasm_report() call after them will be implicitly
reported under the name of the test itself. Note that this only makes sense
if such functions are the last functions being tested in a given test, since
any later checkasm_report() call would otherwise include all prior functions.
@section common_patterns Common Test Patterns
@subsection pattern_2d 2D Buffer Processing
For functions operating on 2D buffers with stride:
@code{.c}
static void check_filter(const DSPContext *dsp)
{
// Define padded 64x64 buffers
BUF_RECT(uint8_t, src, 64, 64);
BUF_RECT(uint8_t, dst_c, 64, 64);
BUF_RECT(uint8_t, dst_a, 64, 64);
checkasm_declare(void, uint8_t *dst, ptrdiff_t dst_stride,
const uint8_t *src, ptrdiff_t src_stride,
int w, int h);
for (int w = 4; w <= 64; w <<= 1) {
if (checkasm_check_func(dsp->filter, "filter_w%d", w)) {
// Check multiple heights
for (int h = 4; h <= 64; h <<= 1) {
INITIALIZE_BUF_RECT(src);
CLEAR_BUF_RECT(dst_c);
CLEAR_BUF_RECT(dst_a);
checkasm_call_ref(dst_c, dst_c_stride, src, src_stride, w, h);
checkasm_call_new(dst_a, dst_a_stride, src, src_stride, w, h);
// Check buffers including padding
checkasm_check_rect_padded(dst_c, dst_c_stride,
dst_a, dst_a_stride,
w, h, "dst");
}
// Benchmark only the full height configuration
checkasm_bench_new(checkasm_alternate(dst_a, dst_c), dst_a_stride,
src, src_stride, w, 64);
}
}
checkasm_report("filter");
}
@endcode
@subsection pattern_state State-Based Functions
For functions that modify internal state or have side effects:
@code{.c}
static void check_decoder(const DecoderContext *dec)
{
DecoderState state_c, state_a;
uint8_t bitstream[128];
// Initialize bitstream with random data
RANDOMIZE_BUF(bitstream);
checkasm_declare(int, DecoderState *state, const uint8_t *data, int len);
if (checkasm_check_func(dec->decode, "decode")) {
// Initialize both states identically
init_decoder_state(&state_c, bitstream, 128);
init_decoder_state(&state_a, bitstream, 128);
// Decode and compare
int result_c = checkasm_call_ref(&state_c, bitstream, 128);
int result_a = checkasm_call_new(&state_a, bitstream, 128);
// Compare return values
if (result_c != result_a) {
if (checkasm_fail()) {
fprintf(stderr, "return value mismatch: %d vs %d\n",
result_c, result_a);
}
}
// Compare final states (optional)
//
// The validity of this check depends on whether or not `DecoderState`
// has padding bytes or non-deterministic internal state.
checkasm_check1d(uint8_t, &state_c, &state_a, sizeof(DecoderState),
"decoder state");
checkasm_bench_new(&state_a, bitstream, 128);
}
checkasm_report("decode");
}
@endcode
@subsection pattern_multi_output Multiple Outputs
For functions that produce multiple output values:
@code{.c}
static void check_stats(const DSPContext *dsp)
{
CHECKASM_ALIGN(uint8_t buf[64 * 64]);
checkasm_declare(int, const uint8_t *buf, int len,
unsigned *variance, unsigned *sum);
if (checkasm_check_func(dsp->compute_stats, "compute_stats")) {
unsigned var_c, var_a, sum_c, sum_a;
INITIALIZE_BUF(buf);
int result_c = checkasm_call_ref(buf, 64*64, &var_c, &sum_c);
int result_a = checkasm_call_new(buf, 64*64, &var_a, &sum_a);
// Compare all outputs
if (result_c != result_a || var_c != var_a || sum_c != sum_a) {
if (checkasm_fail()) {
fprintf(stderr, "result: %d vs %d, var: %u vs %u, sum: %u vs %u\n",
result_c, result_a, var_c, var_a, sum_c, sum_a);
}
}
checkasm_bench_new(buf, 64*64, &var_a, &sum_a);
}
checkasm_report("compute_stats");
}
@endcode
@subsection pattern_custom_input Custom Input Generation
For functions requiring specific test patterns:
@code{.c}
// Generate worst-case inputs to stress the implementation
static void generate_worst_case(uint16_t *buf, int len, int bitdepth_max)
{
// Create reverse sorted sequence of input values
for (int i = 0; i < len; i++)
buf[i] = (len - 1 - i) & bitdepth_max;
}
static void check_transform(const DSPContext *dsp)
{
#define WIDTH 64
CHECKASM_ALIGN(int16_t src [WIDTH]);
CHECKASM_ALIGN(int16_t dst_c[WIDTH]);
CHECKASM_ALIGN(int16_t dst_a[WIDTH]);
checkasm_declare(void, int16_t *dst, const int16_t *src, int width);
if (checkasm_check_func(dsp->transform, "transform")) {
// Test with both random and worst-case inputs
for (int pattern = 0; pattern < 2; pattern++) {
if (pattern == 0) {
INITIALIZE_BUF(src); // Random input
} else {
generate_worst_case(src, WIDTH, 32767); // Worst case pattern
}
CLEAR_BUF(dst_c);
CLEAR_BUF(dst_a);
checkasm_call_ref(dst_c, src, WIDTH);
checkasm_call_new(dst_a, src, WIDTH);
checkasm_check1d(int16_t, dst_c, dst_a, WIDTH, "dst");
}
checkasm_bench_new(checkasm_alternate(dst_a, dst_c), src, WIDTH);
}
checkasm_report("transform");
}
@endcode
@section advanced_topics Advanced Topics
@subsection adv_float Floating-Point Comparison
For functions producing floating-point results, use tolerance-based comparison:
@code{.c}
static void check_float_func(const DSPContext *dsp)
{
#define WIDTH 128
CHECKASM_ALIGN(float src [WIDTH]);
CHECKASM_ALIGN(float dst_c[WIDTH]);
CHECKASM_ALIGN(float dst_a[WIDTH]);
checkasm_declare(void, float *dst, const float *src, int len);
if (checkasm_check_func(dsp->process_float, "process_float")) {
checkasm_randomize_normf(src, WIDTH);
checkasm_call_ref(dst_c, src, WIDTH);
checkasm_call_new(dst_a, src, WIDTH);
// Compare with ULP (Units in Last Place) tolerance
// Note: cannot use checkasm_check1d() here
const int max_ulp = 1;
checkasm_check2d(float_ulp, dst_c, 0, dst_a, 0, WIDTH, 1, "dst", max_ulp);
// Or use absolute epsilon tolerance
// if (!checkasm_float_near_abs_eps_array(dst_c, dst_a, 1e-6f, WIDTH)) {
// checkasm_fail();
// }
checkasm_bench_new(checkasm_alternate(dst_a, dst_c), src, WIDTH);
}
checkasm_report("process_float");
}
@endcode
@subsection adv_padding Padding and Over-Write Detection
Detect when functions write beyond their intended boundaries:
@code{.c}
static void check_bounds(const DSPContext *dsp)
{
BUF_RECT(uint8_t, dst_c, 64, 64);
BUF_RECT(uint8_t, dst_a, 64, 64);
checkasm_declare(void, uint8_t *dst, ptrdiff_t stride, int w, int h);
if (checkasm_check_func(dsp->fill, "fill")) {
const int w = 64, h = 64;
CLEAR_BUF_RECT(dst_c);
CLEAR_BUF_RECT(dst_a);
checkasm_call_ref(dst_c, dst_c_stride, w, h);
checkasm_call_new(dst_a, dst_a_stride, w, h);
// Standard check (no padding)
checkasm_check2d(uint8_t, dst_c, dst_c_stride,
dst_a, dst_a_stride, w, h, "dst");
// Check with padding detection (detects writes outside w×h)
checkasm_check_rect_padded(dst_c, dst_c_stride,
dst_a, dst_a_stride, w, h, "dst");
// Allow over-write up to 16-element alignment on right edge
checkasm_check_rect_padded_align(dst_c, dst_c_stride,
dst_a, dst_a_stride,
w, h, "dst", 16, 1);
checkasm_bench_new(checkasm_alternate(dst_a, dst_c), dst_a_stride, w, h);
}
checkasm_report("fill");
}
@endcode
@subsection bench_multiple Benchmarking Multiple Configurations
For functions that can be benchmarked at multiple configurations:
@code{.c}
if (checkasm_check_func(dsp->filter, "filter_w%d", w)) {
for (int h = 4; h <= 64; h <<= 1) {
// Test all heights for correctness
checkasm_call_ref(dst_c, dst_c_stride, src, src_stride, w, h);
checkasm_call_new(dst_a, dst_a_stride, src, src_stride, w, h);
checkasm_check2d(uint8_t, dst_c, dst_c_stride,
dst_a, dst_a_stride, w, h, "dst");
// Benchmark each configuration
checkasm_bench_new(checkasm_alternate(dst_a, dst_c), dst_a_stride,
src, src_stride, w, h);
}
// The framework will report the geometric mean of all benchmark runs for
// the same checkasm_check_func() call
}
checkasm_report("filter_w%d", w);
@endcode
Alternatively, you could call checkasm_check_func() on each configuration
to get a separate benchmark report for each size, or call checkasm_bench_new()
only on the largest input size to test the limiting behavior.
@subsection adv_bitdepth Multi-Bitdepth Testing
For codecs supporting multiple bit depths:
@code{.c}
static void check_pixfunc(void)
{
DSPContext dsp;
#define WIDTH 64
CHECKASM_ALIGN(uint16_t src [WIDTH]);
CHECKASM_ALIGN(uint16_t dst_c[WIDTH]);
CHECKASM_ALIGN(uint16_t dst_a[WIDTH]);
checkasm_declare(void, uint16_t *dst, const uint16_t *src, int len);
for (int bpc = 10; bpc <= 12; bpc += 2) {
const int bitdepth_max = (1 << bpc) - 1;
dsp_context_init(&dsp, checkasm_get_cpu_flags(), bpc);
if (checkasm_check_func(dsp->process, "process_%dbpc", bpc)) {
// Randomize within valid bit depth range
checkasm_randomize_mask16(src, WIDTH, bitdepth_max);
checkasm_call_ref(dst_c, src, WIDTH);
checkasm_call_new(dst_a, src, WIDTH);
checkasm_check1d(uint16_t, dst_c, dst_a, WIDTH, "dst");
checkasm_bench_new(checkasm_alternate(dst_a, dst_c), src, WIDTH);
}
}
checkasm_report("process");
}
@endcode
Alternatively, you may prefer to compile the test file itself multiple times,
using preprocessor definitions like `-DBITDEPTH=10` etc.
@subsection adv_failure Custom Failure Reporting
Provide detailed diagnostics when tests fail:
@code{.c}
static void check_complex(const DSPContext *dsp)
{
// ...
if (checkasm_check_func(dsp->complex, "complex")) {
for (int param = 0; param < 16; param++) {
int result_c = checkasm_call_ref(param);
int result_a = checkasm_call_new(param);
// Check return value
if (result_c != result_a) {
if (checkasm_fail()) {
// This branch is only executed if verbose error diagnostics
// are requested by the user
fprintf(stderr, "return mismatch for param=%d: %d vs %d\n",
param, result_c, result_a);
}
}
}
}
checkasm_report("complex");
}
@endcode
@subsection adv_indirect Calling Functions Through Wrappers
When the function being tested must be called indirectly through a wrapper,
you may use checkasm_call() and checkasm_call_checked() to invoke an arbitrary
helper function.
In this case, the declared function type must be the type of the wrapper, not
the inner function passed to checkasm_check_func(). You may then access the
untyped reference/tested function pointers via @ref checkasm_key_ref and
@ref checkasm_key_new "":
@code{.c}
typedef int (my_func)(int);
// Wrapper that invokes the actual function
static int sum_upto_n(my_func *func, int count)
{
int sum = 0;
for (int i = 0; i < count; i++)
sum += func(i);
return sum;
}
static void check_wrapper(void)
{
// Declare the signature of the wrapper, not the inner function
checkasm_declare(int, my_func *, int);
if (checkasm_check_func(get_my_func(), "my_wrapped_func")) {
const int count = checkasm_rand() % 100;
// Cast checkasm_key_new to the appropriate type and pass to wrapper
const my_func *my_ref = (my_func *) checkasm_key_ref;
const my_func *my_new = (my_func *) checkasm_key_new;
int sum_c = checkasm_call(sum_upto_n, my_ref, count);
int sum_a = checkasm_call_checked(sum_upto_n, my_new, count);
if (sum_c != sum_a)
checkasm_fail();
}
}
@endcode
@note When using a pattern like this, the value passed to checkasm_check_func()
may not even need to be a function pointer. It could be an arbitrary pointer
or pointer-sized integer, such as a configuration struct or index into a dispatch
table, so long as it uniquely identifies the underlying implementation being
tested.
@subsection adv_mmx MMX Functions (x86)
MMX functions on x86 often omit the `emms` instruction before returning, expecting
the caller to execute it manually after a loop. The `emms` instruction is necessary
to clear MMX state before any floating-point code can execute, but it can be very
slow, so optimized loops that call into MMX kernels usually defer it to the loop
end to minimize overhead.
Use checkasm_declare_emms() for such (non-ABI-compliant) functions:
@code{.c}
static void check_sad_mmx(const DSPContext *dsp)
{
#define SIZE 16
CHECKASM_ALIGN(uint8_t src [SIZE * SIZE]);
CHECKASM_ALIGN(uint8_t ref [SIZE * SIZE]);
// Declaring with CPU_FLAG_MMX enables an automatic emms after calling
// this function whenever CPU_FLAG_MMX is active
checkasm_declare_emms(CPU_FLAG_MMX, int, const uint8_t *src,
const uint8_t *ref, ptrdiff_t stride);
if (checkasm_check_func(dsp->sad_16x16, "sad_16x16")) {
INITIALIZE_BUF(src);
INITIALIZE_BUF(ref);
// checkasm will automatically call emms after checkasm_call_new()
int result_c = checkasm_call_ref(src, ref, SIZE);
int result_a = checkasm_call_new(src, ref, SIZE);
if (result_c != result_a) {
if (checkasm_fail()) {
fprintf(stderr, "sad mismatch: %d vs %d\n", result_c, result_a);
}
}
// checkasm will also call emms after benchmarking iterations
checkasm_bench_new(src, ref, SIZE);
}
checkasm_report("sad");
}
@endcode
The first parameter is a CPU flag mask (e.g., `CPU_FLAG_MMX | CPU_FLAG_MMXEXT`).
When any of the specified CPU flags are active, checkasm will call `emms` after
each checkasm_call_new() and benchmark run. On non-x86 platforms, checkasm_declare_emms()
is equivalent to checkasm_declare().
@note Modern SIMD instruction sets (SSE and later) do not use MMX registers and
therefore don't require `emms`. Only use checkasm_declare_emms() for legacy
MMX-only code or MMXEXT functions that explicitly use MMX registers.
@section tips_tricks Tips and Tricks
@subsection tips_deterministic Deterministic Testing
checkasm uses a seeded PRNG for reproducible tests. To test with a specific seed:
@code{.bash}
./checkasm 12345 # Use seed 12345
@endcode
Failed tests will print the seed used, allowing you to reproduce failures:
@code{.txt}
checkasm: using random seed 987654321
...
sad_16x16: FAILED (ref:1234 new:1235)
@endcode
@subsection tips_selective Selective Testing
Test specific functions or groups:
@code{.bash}
# Test only functions matching pattern
./checkasm --function='add_*'
# Test only a specific test module
./checkasm --test=math
# Combine both
./checkasm --test=dsp --function='blend_*'
@endcode
@subsection tips_verbose Verbose Output
Enable verbose mode for detailed failure information:
@code{.bash}
./checkasm --verbose
@endcode
This shows hexdumps of differing buffer regions automatically, when using
the built-in checkasm_check*() series of buffer comparison helpers.
@subsection tips_bench Benchmarking Tips
Run benchmarks with appropriate duration:
@code{.bash}
# Quick benchmark (default)
./checkasm --bench
# Longer benchmark for more accurate results (10ms per function)
./checkasm --bench --duration=10000
# Export results in different formats
./checkasm --bench --csv > results.csv
./checkasm --bench --json > results.json
./checkasm --bench --html > results.html
@endcode
@subsection tips_helpers Helper Macros
Create helper macros to reduce repetition in your tests:
@code{.c}
#define TEST_FILTER(name, w, h) \
if (checkasm_check_func(dsp->name, #name "_%dx%d", w, h)) { \
test_filter_##name(dsp, w, h); \
checkasm_bench_new(dst, dst_stride, src, src_stride, w, h); \
}
// Usage:
TEST_FILTER(blur, 16, 16);
TEST_FILTER(blur, 32, 32);
TEST_FILTER(sharpen, 16, 16);
@endcode
---
@section tests_next_steps Next Steps
Now that you've mastered writing tests, learn how to accurately measure and
compare the performance of your optimized implementations using checkasm's
benchmarking capabilities.
**Next:** @ref benchmarking

View File

@@ -1,64 +0,0 @@
# A simple makefile for testing building the files in a foreign build
# system. This is not intended as the user facing way of building the library.
SRC_PATH = $(word 1, $(dir $(MAKEFILE_LIST)))
vpath %.c $(SRC_PATH)
vpath %.S $(SRC_PATH)
vpath %.asm $(SRC_PATH)
CPPFLAGS = -I$(SRC_PATH) -I$(SRC_PATH)include -I$(SRC_PATH)src -I$(SRC_PATH)tests
CFLAGS = -std=gnu11 -Wundef $(EXTRA_CFLAGS)
LIBS = -lm
EXE = checkasm-selftest
OBJS = \
src/arm/checkasm_32.o \
src/arm/checkasm_64.o \
src/arm/cpu.o \
src/loongarch/checkasm.o \
src/riscv/callcheck.o \
src/riscv/cpu.o \
src/x86/cpu.o \
src/perf/arm.o \
src/perf/linux.o \
src/perf/macos_kperf.o \
src/checkasm.o \
src/cpu.o \
src/function.o \
src/perf.o \
src/signal.o \
src/stackguard.o \
src/stats.o \
src/utils.o \
tests/selftest.o \
tests/generic.o \
tests/arm/32/tests.o \
tests/arm/32/tests_asm.o \
tests/arm/64/tests.o \
tests/arm/64/tests_asm.o \
tests/riscv/tests.o \
tests/riscv/tests_asm.o \
tests/x86/tests.o
NASM_OBJS = \
src/x86/checkasm.o \
tests/x86/tests_asm.o
NASM_FMT ?=
ifdef NASM_FMT
OBJS += $(NASM_OBJS)
endif
$(EXE): $(OBJS)
$(CC) $(LDFLAGS) -o $@ $^ $(LIBS)
%.o: %.asm
nasm -f $(NASM_FMT) $(CPPFLAGS) -o $@ $<
OBJDIRS = $(sort $(dir $(OBJS)))
$(OBJDIRS):
mkdir -p $@
$(OBJS): | $(OBJDIRS)
clean:
$(RM) $(EXE) $(OBJS) $(NASM_OBJS)

View File

@@ -1,63 +0,0 @@
#!/bin/sh
# Copyright © 2026, Martin Storsjo
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
set -e
LSCPU="$1"
if [ -z "$LSCPU" ]; then
echo $0 lscpu-binary
exit 1
fi
make_symbol() {
echo "$1" | sed 's/[^A-Za-z_]/_/g' | tr A-Z a-z
}
echo "/* Autogenerated with gen-arm-core-table.sh */"
echo "/* clang-format off */"
"$LSCPU" --arm-id | tail -n +2 | while read line; do
id=$(echo $line | cut -f 1 -d " ")
name=$(echo $line | cut -f 2- -d " ")
echo "static const struct arm_core cores_$(make_symbol "$name")[] = {"
"$LSCPU" --arm-id=$id | tail -n +2 | while read line; do
core_id=$(echo $line | cut -f 1 -d " ")
core_name=$(echo $line | cut -f 2- -d " ")
echo " { $core_id, \"$core_name\" },"
done
echo " { 0, NULL },"
echo "};"
echo ""
done
echo "static const struct arm_implementer arm_implementers[] = {"
"$LSCPU" --arm-id | tail -n +2 | while read line; do
id=$(echo $line | cut -f 1 -d " ")
name=$(echo $line | cut -f 2- -d " ")
echo " { $id, cores_$(make_symbol "$name"), \"$name\" },"
done
echo " { 0, NULL, NULL },"
echo "};"

View File

@@ -1,65 +0,0 @@
# Copyright © 2025 Niklas Haas
# Copyright © 2018, VideoLAN and dav1d authors
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
api_header_file_names = [
'attributes.h',
'checkasm.h',
'test.h',
'utils.h',
]
extra_header_names = [
'header_config.h',
'perf.h',
'perf/aarch64.h',
'perf/arm.h',
'perf/loongarch32.h',
'perf/loongarch64.h',
'perf/ppc64le.h',
'perf/x86.h',
'platform.h',
'platform/aarch64.h',
'platform/arm.h',
'platform/loongarch.h',
'platform/ppc64le.h',
'platform/riscv.h',
'platform/x86_32.h',
'platform/x86_64.h',
]
checkasm_api_headers = files(api_header_file_names + extra_header_names)
# Export these for the Doxygen build process (see docs/meson.build)
checkasm_include_dir = meson.current_source_dir()
checkasm_api_header_paths = []
foreach file_name : api_header_file_names
checkasm_api_header_paths += checkasm_include_dir / file_name
endforeach
if not meson.is_subproject()
install_headers(checkasm_api_headers, subdir: meson.project_name(), preserve_path: true)
endif

View File

@@ -1,134 +0,0 @@
# Copyright © 2025 Niklas Haas
# Copyright © 2024, Marvin Scholz
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
project('checkasm', 'c',
version: '1.3.0',
default_options: ['c_std=c11',
'buildtype=debugoptimized',
'default_library=static',
'b_ndebug=if-release',
'warning_level=2'],
meson_version : '>=0.64.0')
# Project arguments and basic checks
cc = meson.get_compiler('c')
optional_arguments = []
if cc.get_argument_syntax() != 'msvc'
optional_arguments += [
'-Wundef',
'-Werror=vla',
'-Wno-maybe-uninitialized',
'-Wno-missing-field-initializers',
'-Wno-unused-parameter',
'-Wstrict-prototypes',
'-Werror=missing-prototypes',
'-Wshorten-64-to-32',
'-Wtype-limits',
'-Wredundant-decls',
'-Wimplicit-fallthrough=5',
]
else
optional_arguments += [
'-wd4028', # parameter different from declaration
'-wd4996', # use of POSIX functions
]
endif
add_project_arguments(cc.get_supported_arguments(optional_arguments), language : 'c')
test_args = []
if host_machine.system() in ['linux', 'gnu']
add_project_arguments(['-D_POSIX_C_SOURCE=200112', '-D_XOPEN_SOURCE=600'], language: 'c')
test_args += ['-D_POSIX_C_SOURCE=200112', '-D_XOPEN_SOURCE=600']
endif
if host_machine.system() in ['emscripten', 'os/2']
add_project_arguments('-D_GNU_SOURCE', language: 'c')
test_args += '-D_GNU_SOURCE'
endif
if get_option('b_sanitize') == 'memory'
error('asm causes false positives with the memory sanitizer.')
endif
if host_machine.system() not in ['windows', 'os/2']
if cc.has_argument('-fvisibility=hidden')
add_project_arguments('-fvisibility=hidden', language: 'c')
else
warning('Compiler does not support -fvisibility=hidden, all symbols will be public!')
endif
endif
# Global paths
checkasm_src_root = meson.current_source_dir()
checkasm_inc_dirs = include_directories('.', 'include')
# Global platform flags
arch_aarch64 = host_machine.cpu_family() == 'aarch64' or host_machine.cpu() == 'arm64'
arch_arm = host_machine.cpu_family().startswith('arm') and host_machine.cpu() != 'arm64'
arch_x86 = host_machine.cpu_family().startswith('x86')
arch_x86_64 = host_machine.cpu_family() == 'x86_64'
arch_x86_32 = host_machine.cpu_family() == 'x86'
arch_ppc64le = host_machine.cpu() == 'ppc64le'
arch_riscv = host_machine.cpu_family().startswith('riscv')
arch_rv32 = host_machine.cpu_family() == 'riscv32'
arch_rv64 = host_machine.cpu_family() == 'riscv64'
arch_loongarch = host_machine.cpu_family().startswith('loongarch')
arch_loongarch32 = host_machine.cpu_family() == 'loongarch32'
arch_loongarch64 = host_machine.cpu_family() == 'loongarch64'
use_gaspp = false
if ((arch_aarch64 or arch_arm) and
cc.get_argument_syntax() == 'msvc' and cc.get_id() != 'clang-cl')
gaspp = find_program('gas-preprocessor.pl')
use_gaspp = true
gaspp_args = [
'-as-type', 'armasm',
'-arch', host_machine.cpu_family(),
'--',
arch_aarch64 ? 'armasm64' : 'armasm',
'-nologo',
]
gaspp_gen = generator(gaspp,
output: '@BASENAME@.obj',
arguments: gaspp_args + [
'@EXTRA_ARGS@',
'@INPUT@',
'-c',
'-o', '@OUTPUT@'
])
endif
# Global component configuration
build_tests = get_option('tests').disable_auto_if(meson.is_subproject())
build_docs = get_option('docs' ).disable_auto_if(meson.is_subproject())
subdir('include/checkasm')
subdir('src')
subdir('docs')
subdir('tests')
meson.override_dependency(meson.project_name(), checkasm_dep)

View File

@@ -1,35 +0,0 @@
# Copyright © 2025 Niklas Haas
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
option('docs',
type: 'feature',
value: 'auto',
description: 'Build API documentation with Doxygen'
)
option('tests',
type: 'feature',
value: 'auto',
description: 'Build internal self-tests'
)

View File

@@ -1,261 +0,0 @@
# Copyright © 2018, VideoLAN and dav1d authors
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
# Dependencies
libm_dependency = cc.find_library('m', required: false)
rt_dependency = cc.find_library('rt', required: false)
libdl_dependency = cc.find_library('dl', required: false)
thread_dependency = dependency('threads')
have_pthread_np = cc.check_header('pthread_np.h')
if have_pthread_np
test_args += '-DHAVE_PTHREAD_NP_H'
endif
have_pthread_setaffinity_np = cc.has_function('pthread_setaffinity_np',
prefix: '''
#include <pthread.h>
#ifdef HAVE_PTHREAD_NP_H
#include <pthread_np.h>
#endif
''',
args: test_args + '-D_GNU_SOURCE',
dependencies: thread_dependency,
)
if not have_pthread_setaffinity_np
thread_dependency = []
endif
have_clock_gettime = cc.has_function('clock_gettime', prefix : '#include <time.h>', args : test_args, dependencies : rt_dependency)
have_ioctl = cc.has_function('ioctl', prefix : '#include <sys/ioctl.h>', args : test_args)
have_isatty = cc.has_function('isatty', prefix : '#include <unistd.h>', args : test_args)
have_prctl = cc.has_function('prctl', prefix : '#include <sys/prctl.h>', args : test_args)
have_sigaction = cc.has_function('sigaction', prefix : '#include <signal.h>', args : test_args)
have_siglongjmp = cc.has_function('siglongjmp', prefix : '#include <setjmp.h>', args : test_args)
have_getauxval = false
have_elf_aux_info = false
if (arch_aarch64 or arch_arm or arch_loongarch or arch_ppc64le or arch_riscv)
have_getauxval = cc.has_function('getauxval', prefix : '#include <sys/auxv.h>', args : test_args)
have_elf_aux_info = cc.has_function('elf_aux_info', prefix : '#include <sys/auxv.h>', args : test_args)
endif
have_stdbit_h = cc.check_header('stdbit.h')
have_sys_hwprobe_h = false
have_asm_hwprobe_h = false
if arch_riscv
have_sys_hwprobe_h = cc.check_header('sys/hwprobe.h')
have_asm_hwprobe_h = cc.check_header('asm/hwprobe.h')
endif
api_export_flags = []
api_export_flags_asm = []
if host_machine.system() == 'windows'
if get_option('default_library') != 'static'
# nasm's dllexport doesn't match the mingw conventions on x86_32,
# so don't do explicit dllexport at all, just let it export all symbols.
if cc.get_argument_syntax() == 'msvc' or not arch_x86_32
api_export_flags = ['-DCHECKASM_BUILDING_DLL', '-DBUILDING_DLL']
api_export_flags_asm = ['-DBUILDING_DLL']
endif
endif
endif
if build_tests.allowed()
api_export_flags += '-DCHECKASM_BUILDING_TESTS'
api_export_flags_asm += '-DCHECKASM_BUILDING_TESTS'
endif
have_linux_perf = false
if host_machine.system() == 'linux'
have_linux_perf = cc.has_header_symbol('linux/perf_event.h', 'PERF_COUNT_HW_CPU_CYCLES', args: test_args)
endif
# Build configuration
cdata = configuration_data()
cdata.set('PREFIX', cc.symbols_have_underscore_prefix())
cdata.set_quoted('CHECKASM_VERSION', meson.project_version())
cdata.set10('ARCH_AARCH64', arch_aarch64)
cdata.set10('ARCH_ARM', arch_arm)
cdata.set10('ARCH_X86', arch_x86)
cdata.set10('ARCH_X86_64', arch_x86_64)
cdata.set10('ARCH_X86_32', arch_x86_32)
cdata.set10('ARCH_PPC64LE', arch_ppc64le)
cdata.set10('ARCH_RISCV', arch_riscv)
cdata.set10('ARCH_RV32', arch_rv32)
cdata.set10('ARCH_RV64', arch_rv64)
cdata.set10('ARCH_LOONGARCH', arch_loongarch)
cdata.set10('ARCH_LOONGARCH32', arch_loongarch32)
cdata.set10('ARCH_LOONGARCH64', arch_loongarch64)
cdata.set10('HAVE_PTHREAD_NP_H', have_pthread_np)
cdata.set10('HAVE_PTHREAD_SETAFFINITY_NP', have_pthread_setaffinity_np)
cdata.set10('HAVE_CLOCK_GETTIME', have_clock_gettime)
cdata.set10('HAVE_IOCTL', have_ioctl)
cdata.set10('HAVE_ISATTY', have_isatty)
cdata.set10('HAVE_SIGACTION', have_sigaction)
cdata.set10('HAVE_SIGLONGJMP', have_siglongjmp)
cdata.set10('HAVE_GETAUXVAL', have_getauxval)
cdata.set10('HAVE_ELF_AUX_INFO', have_elf_aux_info)
cdata.set10('HAVE_LINUX_PERF', have_linux_perf)
cdata.set10('HAVE_STDBIT_H', have_stdbit_h)
cdata.set10('HAVE_PRCTL', have_prctl)
if arch_x86
cdata_asm = configuration_data()
cdata_asm.set('PREFIX', cc.symbols_have_underscore_prefix())
cdata_asm.set10('ARCH_X86_64', arch_x86_64)
cdata_asm.set10('ARCH_X86_32', arch_x86_32)
cdata_asm.set10('PIC', true)
cdata_asm.set10('FORCE_VEX_ENCODING', cc.get_define('__AVX__').strip() != '')
endif
if arch_aarch64
if use_gaspp
python3 = import('python').find_installation()
endif
aarch64_extensions = {
'sve': ['whilelt p0.s, x0, x1'],
'sme': ['smstart', 'cntb x0'],
}
foreach name, instrs : aarch64_extensions
if use_gaspp
instr = '\n'.join(instrs)
f = configure_file(
command: [python3, '-c', 'import sys; print(sys.argv[1])', '@0@'.format(instr)],
output: 'test-@0@.S'.format(name),
capture: true)
r = run_command(gaspp, gaspp_args, f, '-c', '-o', meson.current_build_dir() / 'test-' + name + '.obj', check: false)
message('Checking for gaspp/armasm64 ' + name.to_upper() + ': ' + (r.returncode() == 0 ? 'YES' : 'NO'))
archext_supported = false
supported = r.returncode() == 0
else
instr = '\\n'.join(instrs)
code = f'__asm__ (".arch_extension @name@\\n@instr@\\n");'
supported = cc.compiles(code, name: name.to_upper())
archext_supported = supported
endif
cdata.set10('HAVE_AS_ARCHEXT_' + name.to_upper() + '_DIRECTIVE', archext_supported)
cdata.set10('HAVE_' + name.to_upper(), supported)
endforeach
endif
if arch_riscv
cdata.set10('HAVE_SYS_HWPROBE_H', have_sys_hwprobe_h)
cdata.set10('HAVE_ASM_HWPROBE_H', have_asm_hwprobe_h)
endif
checkasm_config = []
checkasm_config += configure_file(
output: 'checkasm_config_generated.h',
configuration: cdata,
install: false,
)
# Build definition
checkasm_asm_objs = []
checkasm_sources = files(
'arm/cpu.c',
'checkasm.c',
'cpu.c',
'function.c',
'perf.c',
'perf/arm.c',
'perf/linux.c',
'perf/macos_kperf.c',
'riscv/cpu.c',
'signal.c',
'stackguard.c',
'stats.c',
'utils.c',
'x86/cpu.c',
)
checkasm_asm_sources = []
if arch_aarch64
checkasm_asm_sources = files('arm/checkasm_64.S')
elif arch_arm
checkasm_asm_sources = files('arm/checkasm_32.S')
elif arch_loongarch
checkasm_sources += files('loongarch/checkasm.S')
elif arch_riscv
checkasm_sources += files('riscv/callcheck.S')
elif arch_x86
add_languages('nasm', native: false)
configure_file(
output: 'checkasm_config_generated.asm',
output_format: 'nasm',
configuration: cdata_asm,
install: false,
)
checkasm_sources += files(
'x86/checkasm.asm',
)
endif
if use_gaspp
gaspp_args = api_export_flags_asm + [
'-I@0@'.format(meson.project_source_root() + '/include'),
'-I@0@'.format(meson.current_source_dir()),
'-I@0@'.format(meson.current_build_dir()),
'-DCHECKASM_HAVE_GENERATED_H',
]
checkasm_asm_objs += gaspp_gen.process(checkasm_asm_sources, extra_args: gaspp_args)
else
checkasm_sources += checkasm_asm_sources
endif
checkasm_dependencies = [
thread_dependency,
rt_dependency,
libdl_dependency,
libm_dependency,
]
checkasm_library = library(meson.project_name(),
checkasm_sources,
checkasm_config,
checkasm_asm_objs,
c_args : [api_export_flags, '-DCHECKASM_HAVE_GENERATED_H'],
nasm_args : [api_export_flags_asm, '-DCHECKASM_HAVE_GENERATED_H'],
include_directories: checkasm_inc_dirs,
dependencies: checkasm_dependencies,
install: not meson.is_subproject(),
)
checkasm_dep = declare_dependency(
link_with: checkasm_library,
include_directories: checkasm_inc_dirs,
)
if not meson.is_subproject()
pkg = import('pkgconfig')
pkg.generate(
name: meson.project_name(),
description: 'checkasm for all your asm checking needs',
libraries: checkasm_library,
version: meson.project_version(),
)
endif

View File

@@ -1,222 +0,0 @@
#include "tests.h"
#if ARCH_ARM
/* Re-use helpers from main checkasm library */
#include "src/cpu.h"
uint64_t selftest_get_cpu_flags_arm(void)
{
uint64_t flags = SELFTEST_CPU_FLAG_ARM;
if (checkasm_has_vfp())
flags |= SELFTEST_CPU_FLAG_VFP;
if (checkasm_has_vfpd32())
flags |= SELFTEST_CPU_FLAG_VFPD32;
return flags;
}
DEF_NOOP_FUNC(clobber_r0);
DEF_NOOP_FUNC(clobber_r1);
DEF_NOOP_FUNC(clobber_r2);
DEF_NOOP_FUNC(clobber_r3);
DEF_NOOP_FUNC(clobber_r4);
DEF_NOOP_FUNC(clobber_r5);
DEF_NOOP_FUNC(clobber_r6);
DEF_NOOP_FUNC(clobber_r7);
DEF_NOOP_FUNC(clobber_r8);
DEF_NOOP_FUNC(clobber_r9);
DEF_NOOP_FUNC(clobber_r10);
DEF_NOOP_FUNC(clobber_r11);
DEF_NOOP_FUNC(clobber_r12);
DEF_NOOP_FUNC(clobber_d0);
DEF_NOOP_FUNC(clobber_d1);
DEF_NOOP_FUNC(clobber_d2);
DEF_NOOP_FUNC(clobber_d3);
DEF_NOOP_FUNC(clobber_d4);
DEF_NOOP_FUNC(clobber_d5);
DEF_NOOP_FUNC(clobber_d6);
DEF_NOOP_FUNC(clobber_d7);
DEF_NOOP_FUNC(clobber_d8);
DEF_NOOP_FUNC(clobber_d9);
DEF_NOOP_FUNC(clobber_d10);
DEF_NOOP_FUNC(clobber_d11);
DEF_NOOP_FUNC(clobber_d12);
DEF_NOOP_FUNC(clobber_d13);
DEF_NOOP_FUNC(clobber_d14);
DEF_NOOP_FUNC(clobber_d15);
DEF_NOOP_FUNC(clobber_d16);
DEF_NOOP_FUNC(clobber_d17);
DEF_NOOP_FUNC(clobber_d18);
DEF_NOOP_FUNC(clobber_d19);
DEF_NOOP_FUNC(clobber_d20);
DEF_NOOP_FUNC(clobber_d21);
DEF_NOOP_FUNC(clobber_d22);
DEF_NOOP_FUNC(clobber_d23);
DEF_NOOP_FUNC(clobber_d24);
DEF_NOOP_FUNC(clobber_d25);
DEF_NOOP_FUNC(clobber_d26);
DEF_NOOP_FUNC(clobber_d27);
DEF_NOOP_FUNC(clobber_d28);
DEF_NOOP_FUNC(clobber_d29);
DEF_NOOP_FUNC(clobber_d30);
DEF_NOOP_FUNC(clobber_d31);
DEF_NOOP_FUNC(sigill_arm);
DEF_NOOP_FUNC(clobber_fpscr_vfp);
// A function with 4 parameters in registers and 1 on the stack.
typedef void(many_args_func)(int, int, int, int, int);
#define DEF_MANY_ARGS_FUNC(NAME) void selftest_##NAME(int, int, int, int, int)
#define DEF_MANY_ARGS_GETTER(FLAG, NAME) DEF_GETTER(FLAG, NAME, many_args_func, NULL)
DEF_MANY_ARGS_FUNC(clobber_stack_args_arm);
DEF_MANY_ARGS_FUNC(clobber_stack_arm);
static noop_func *get_clobber_r(int reg)
{
if (!(checkasm_get_cpu_flags() & SELFTEST_CPU_FLAG_ARM))
return NULL;
switch (reg) {
case 0: return selftest_clobber_r0;
case 1: return selftest_clobber_r1;
case 2: return selftest_clobber_r2;
case 3: return selftest_clobber_r3;
case 4: return selftest_clobber_r4;
case 5: return selftest_clobber_r5;
case 6: return selftest_clobber_r6;
case 7: return selftest_clobber_r7;
case 8: return selftest_clobber_r8;
case 9: return selftest_clobber_r9;
case 10: return selftest_clobber_r10;
case 11: return selftest_clobber_r11;
case 12: return selftest_clobber_r12;
case 13: return NULL; /* Shouldn't try to clobber SP */
case 14: return NULL; /* Shouldn't try to clobber LR */
case 15: return NULL; /* Shouldn't try to clobber PC */
default: return NULL;
}
}
static noop_func *get_clobber_d(int reg)
{
if (!(checkasm_get_cpu_flags() & SELFTEST_CPU_FLAG_VFP))
return NULL;
if (reg >= 16 && !(checkasm_get_cpu_flags() & SELFTEST_CPU_FLAG_VFPD32))
return NULL;
switch (reg) {
case 0: return selftest_clobber_d0;
case 1: return selftest_clobber_d1;
case 2: return selftest_clobber_d2;
case 3: return selftest_clobber_d3;
case 4: return selftest_clobber_d4;
case 5: return selftest_clobber_d5;
case 6: return selftest_clobber_d6;
case 7: return selftest_clobber_d7;
case 8: return selftest_clobber_d8;
case 9: return selftest_clobber_d9;
case 10: return selftest_clobber_d10;
case 11: return selftest_clobber_d11;
case 12: return selftest_clobber_d12;
case 13: return selftest_clobber_d13;
case 14: return selftest_clobber_d14;
case 15: return selftest_clobber_d15;
case 16: return selftest_clobber_d16;
case 17: return selftest_clobber_d17;
case 18: return selftest_clobber_d18;
case 19: return selftest_clobber_d19;
case 20: return selftest_clobber_d20;
case 21: return selftest_clobber_d21;
case 22: return selftest_clobber_d22;
case 23: return selftest_clobber_d23;
case 24: return selftest_clobber_d24;
case 25: return selftest_clobber_d25;
case 26: return selftest_clobber_d26;
case 27: return selftest_clobber_d27;
case 28: return selftest_clobber_d28;
case 29: return selftest_clobber_d29;
case 30: return selftest_clobber_d30;
case 31: return selftest_clobber_d31;
default: return NULL;
}
}
DEF_NOOP_GETTER(SELFTEST_CPU_FLAG_ARM, sigill_arm)
DEF_NOOP_GETTER(SELFTEST_CPU_FLAG_VFP, clobber_fpscr_vfp)
DEF_MANY_ARGS_GETTER(SELFTEST_CPU_FLAG_ARM, clobber_stack_args_arm)
DEF_MANY_ARGS_GETTER(SELFTEST_CPU_FLAG_ARM, clobber_stack_arm)
static void check_clobber_r(int from, int to)
{
checkasm_declare(void, int);
for (int reg = from; reg < to; reg++) {
noop_func *clobber = get_clobber_r(reg);
if (!clobber)
break;
if (checkasm_check_func(clobber, "clobber_r%d", reg)) {
checkasm_call_new(0);
}
}
checkasm_report("clobber_r");
}
static void check_clobber_d(int from, int to)
{
checkasm_declare(void, int);
for (int reg = from; reg < to; reg++) {
noop_func *clobber = get_clobber_d(reg);
if (!clobber)
break;
if (checkasm_check_func(clobber, "clobber_d%d", reg)) {
checkasm_call_new(0);
}
}
checkasm_report("clobber_d");
}
static void selftest_test_many_args(many_args_func fun, const char *name)
{
checkasm_declare(void, int, int, int, int, int);
if (checkasm_check_func(fun, "%s", name)) {
/* don't call unchecked because that one is called without wrapping,
* and we try to cloober the stack here. */
(void) func_ref;
checkasm_call_new(1, 2, 3, 4, 5);
}
checkasm_report("%s", name);
}
void selftest_check_arm(void)
{
check_clobber_r(0, 4);
check_clobber_r(12, 13);
check_clobber_d(0, 8);
check_clobber_d(16, 32);
selftest_test_many_args(get_clobber_stack_args_arm(), "clobber_stack_args");
if (!checkasm_should_fail(SELFTEST_CPU_FLAG_ARM))
return;
selftest_test_noop(get_sigill_arm(), "sigill");
selftest_test_many_args(get_clobber_stack_arm(), "clobber_stack");
selftest_test_noop(get_clobber_fpscr_vfp(), "clobber_fpscr");
check_clobber_r(4, 9);
#ifndef __APPLE__
// In the iOS ABI, r9 is a volatile register, so we don't check if it is
// clobbered in the checked_call wrapper.
check_clobber_r(9, 10);
#endif
check_clobber_r(10, 11);
check_clobber_d(8, 16);
}
#endif

View File

@@ -1,108 +0,0 @@
/******************************************************************************
* Copyright © 2025 Martin Storsjo
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
#define PRIVATE_PREFIX selftest_
#include "src/checkasm_config.h"
#if ARCH_ARM
#include "src/arm/asm.S"
#ifndef __ARM_ARCH
#ifdef _M_ARM
#define __ARM_ARCH _M_ARM
#else
#define __ARM_ARCH 0
#endif
#endif
.macro v4bx rd
#if __ARM_ARCH >= 5 || defined(__ARM_ARCH_4T__)
bx \rd
#else
mov pc, \rd
#endif
.endm
function sigill_arm, export=1
udf #0
v4bx lr
endfunc
.macro clobber_r reg
function clobber_\reg, export=1
mov \reg, #0x42
v4bx lr
endfunc
.endm
.macro clobber_d reg
function clobber_\reg, export=1
mov r0, #0x42
mov r1, #0x42
vmov \reg, r0, r1
v4bx lr
endfunc
.endm
.irp r, r0, r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, r11, r12
clobber_r \r
.endr
.irp r, d0, d1, d2, d3, d4, d5, d6, d7, d8, d9, d10, d11, d12, d13, d14, d15, d16, d17, d18, d19, d20, d21, d22, d23, d24, d25, d26, d27, d28, d29, d30, d31
clobber_d \r
.endr
// void clobber_stack_args_arm(int, int, int, int, int);
//
// Write into the stack of the caller, overwriting the arguments that are on
// the stack. This is legal to do.
function clobber_stack_args_arm, export=1
mov r0, #0x42
str r0, [sp, #0]
v4bx lr
endfunc
// void clobber_stack_arm(int, int, int, int, int);
//
// Write into the stack of the caller, writing past the arguments on the stack
// that we are allowed to touch.
function clobber_stack_arm, export=1
mov r0, #0x42
str r0, [sp, #0]
str r0, [sp, #4]
v4bx lr
endfunc
function clobber_fpscr_vfp, export=1
// Flush to zero, default NaN mode, round towards zero
ldr r12, =0x3C00000
fmxr fpscr, r12
v4bx lr
endfunc
#endif

View File

@@ -1,387 +0,0 @@
#include "tests.h"
#if ARCH_AARCH64
uint64_t selftest_get_cpu_flags_aarch64(void)
{
return SELFTEST_CPU_FLAG_AARCH64;
}
DEF_NOOP_FUNC(clobber_x0);
DEF_NOOP_FUNC(clobber_x1);
DEF_NOOP_FUNC(clobber_x2);
DEF_NOOP_FUNC(clobber_x3);
DEF_NOOP_FUNC(clobber_x4);
DEF_NOOP_FUNC(clobber_x5);
DEF_NOOP_FUNC(clobber_x6);
DEF_NOOP_FUNC(clobber_x7);
DEF_NOOP_FUNC(clobber_x8);
DEF_NOOP_FUNC(clobber_x9);
DEF_NOOP_FUNC(clobber_x10);
DEF_NOOP_FUNC(clobber_x11);
DEF_NOOP_FUNC(clobber_x12);
DEF_NOOP_FUNC(clobber_x13);
DEF_NOOP_FUNC(clobber_x14);
DEF_NOOP_FUNC(clobber_x15);
DEF_NOOP_FUNC(clobber_x16);
DEF_NOOP_FUNC(clobber_x17);
// x18 skipped
DEF_NOOP_FUNC(clobber_x19);
DEF_NOOP_FUNC(clobber_x20);
DEF_NOOP_FUNC(clobber_x21);
DEF_NOOP_FUNC(clobber_x22);
DEF_NOOP_FUNC(clobber_x23);
DEF_NOOP_FUNC(clobber_x24);
DEF_NOOP_FUNC(clobber_x25);
DEF_NOOP_FUNC(clobber_x26);
DEF_NOOP_FUNC(clobber_x27);
DEF_NOOP_FUNC(clobber_x28);
DEF_NOOP_FUNC(clobber_x29);
DEF_NOOP_FUNC(clobber_d0);
DEF_NOOP_FUNC(clobber_d1);
DEF_NOOP_FUNC(clobber_d2);
DEF_NOOP_FUNC(clobber_d3);
DEF_NOOP_FUNC(clobber_d4);
DEF_NOOP_FUNC(clobber_d5);
DEF_NOOP_FUNC(clobber_d6);
DEF_NOOP_FUNC(clobber_d7);
DEF_NOOP_FUNC(clobber_d8);
DEF_NOOP_FUNC(clobber_d9);
DEF_NOOP_FUNC(clobber_d10);
DEF_NOOP_FUNC(clobber_d11);
DEF_NOOP_FUNC(clobber_d12);
DEF_NOOP_FUNC(clobber_d13);
DEF_NOOP_FUNC(clobber_d14);
DEF_NOOP_FUNC(clobber_d15);
DEF_NOOP_FUNC(clobber_d16);
DEF_NOOP_FUNC(clobber_d17);
DEF_NOOP_FUNC(clobber_d18);
DEF_NOOP_FUNC(clobber_d19);
DEF_NOOP_FUNC(clobber_d20);
DEF_NOOP_FUNC(clobber_d21);
DEF_NOOP_FUNC(clobber_d22);
DEF_NOOP_FUNC(clobber_d23);
DEF_NOOP_FUNC(clobber_d24);
DEF_NOOP_FUNC(clobber_d25);
DEF_NOOP_FUNC(clobber_d26);
DEF_NOOP_FUNC(clobber_d27);
DEF_NOOP_FUNC(clobber_d28);
DEF_NOOP_FUNC(clobber_d29);
DEF_NOOP_FUNC(clobber_d30);
DEF_NOOP_FUNC(clobber_d31);
DEF_NOOP_FUNC(clobber_v8_upper);
DEF_NOOP_FUNC(clobber_v9_upper);
DEF_NOOP_FUNC(clobber_v10_upper);
DEF_NOOP_FUNC(clobber_v11_upper);
DEF_NOOP_FUNC(clobber_v12_upper);
DEF_NOOP_FUNC(clobber_v13_upper);
DEF_NOOP_FUNC(clobber_v14_upper);
DEF_NOOP_FUNC(clobber_v15_upper);
DEF_NOOP_FUNC(sigill_aarch64);
// A function with 8 parameters in registers and 3 on the stack.
typedef void(many_args_func)(int, int, int, int, int, int, int, int, int, int, int);
#define DEF_MANY_ARGS_FUNC(NAME) \
void selftest_##NAME(int, int, int, int, int, int, int, int, int, int, int)
#define DEF_MANY_ARGS_GETTER(FLAG, NAME) DEF_GETTER(FLAG, NAME, many_args_func, NULL)
DEF_MANY_ARGS_FUNC(clobber_stack_args_aarch64);
DEF_MANY_ARGS_FUNC(clobber_stack_aarch64);
DEF_MANY_ARGS_FUNC(check_clobber_upper_x0_aarch64);
DEF_MANY_ARGS_FUNC(check_clobber_upper_x1_aarch64);
DEF_MANY_ARGS_FUNC(check_clobber_upper_x2_aarch64);
DEF_MANY_ARGS_FUNC(check_clobber_upper_x3_aarch64);
DEF_MANY_ARGS_FUNC(check_clobber_upper_x4_aarch64);
DEF_MANY_ARGS_FUNC(check_clobber_upper_x5_aarch64);
DEF_MANY_ARGS_FUNC(check_clobber_upper_x6_aarch64);
DEF_MANY_ARGS_FUNC(check_clobber_upper_x7_aarch64);
DEF_MANY_ARGS_FUNC(check_clobber_upper_stack0_aarch64);
DEF_MANY_ARGS_FUNC(check_clobber_upper_stack1_aarch64);
DEF_MANY_ARGS_FUNC(check_clobber_upper_stack2_aarch64);
// A function with MAX_ARGS (15) arguments
typedef void(max_int_args_func)(int, int, int, int, int, int, int, int, int, int, int,
int, int, int, int);
typedef void(max_int64_args_func)(int64_t, int64_t, int64_t, int64_t, int64_t, int64_t,
int64_t, int64_t, int64_t, int64_t, int64_t, int64_t,
int64_t, int64_t, int64_t);
#define DEF_MAX_INT_ARGS_FUNC(NAME) \
void selftest_##NAME(int, int, int, int, int, int, int, int, int, int, int, int, \
int, int, int)
#define DEF_MAX_INT_ARGS_GETTER(FLAG, NAME) \
DEF_GETTER(FLAG, NAME, max_int_args_func, NULL)
#define DEF_MAX_INT64_ARGS_FUNC(NAME) \
void selftest_##NAME(int64_t, int64_t, int64_t, int64_t, int64_t, int64_t, int64_t, \
int64_t, int64_t, int64_t, int64_t, int64_t, int64_t, int64_t, \
int64_t)
#define DEF_MAX_INT64_ARGS_GETTER(FLAG, NAME) \
DEF_GETTER(FLAG, NAME, max_int64_args_func, NULL)
DEF_MAX_INT_ARGS_FUNC(check_max_int_args_aarch64);
DEF_MAX_INT64_ARGS_FUNC(check_max_int64_args_aarch64);
static noop_func *get_clobber_x(int reg)
{
if (!(checkasm_get_cpu_flags() & SELFTEST_CPU_FLAG_AARCH64))
return NULL;
switch (reg) {
case 0: return selftest_clobber_x0;
case 1: return selftest_clobber_x1;
case 2: return selftest_clobber_x2;
case 3: return selftest_clobber_x3;
case 4: return selftest_clobber_x4;
case 5: return selftest_clobber_x5;
case 6: return selftest_clobber_x6;
case 7: return selftest_clobber_x7;
case 8: return selftest_clobber_x8;
case 9: return selftest_clobber_x9;
case 10: return selftest_clobber_x10;
case 11: return selftest_clobber_x11;
case 12: return selftest_clobber_x12;
case 13: return selftest_clobber_x13;
case 14: return selftest_clobber_x14;
case 15: return selftest_clobber_x15;
case 16: return selftest_clobber_x16;
case 17: return selftest_clobber_x17;
// x18 skipped
case 19: return selftest_clobber_x19;
case 20: return selftest_clobber_x20;
case 21: return selftest_clobber_x21;
case 22: return selftest_clobber_x22;
case 23: return selftest_clobber_x23;
case 24: return selftest_clobber_x24;
case 25: return selftest_clobber_x25;
case 26: return selftest_clobber_x26;
case 27: return selftest_clobber_x27;
case 28: return selftest_clobber_x28;
case 29: return selftest_clobber_x29;
default: return NULL;
}
}
static noop_func *get_clobber_d(int reg)
{
if (!(checkasm_get_cpu_flags() & SELFTEST_CPU_FLAG_AARCH64))
return NULL;
switch (reg) {
case 0: return selftest_clobber_d0;
case 1: return selftest_clobber_d1;
case 2: return selftest_clobber_d2;
case 3: return selftest_clobber_d3;
case 4: return selftest_clobber_d4;
case 5: return selftest_clobber_d5;
case 6: return selftest_clobber_d6;
case 7: return selftest_clobber_d7;
case 8: return selftest_clobber_d8;
case 9: return selftest_clobber_d9;
case 10: return selftest_clobber_d10;
case 11: return selftest_clobber_d11;
case 12: return selftest_clobber_d12;
case 13: return selftest_clobber_d13;
case 14: return selftest_clobber_d14;
case 15: return selftest_clobber_d15;
case 16: return selftest_clobber_d16;
case 17: return selftest_clobber_d17;
case 18: return selftest_clobber_d18;
case 19: return selftest_clobber_d19;
case 20: return selftest_clobber_d20;
case 21: return selftest_clobber_d21;
case 22: return selftest_clobber_d22;
case 23: return selftest_clobber_d23;
case 24: return selftest_clobber_d24;
case 25: return selftest_clobber_d25;
case 26: return selftest_clobber_d26;
case 27: return selftest_clobber_d27;
case 28: return selftest_clobber_d28;
case 29: return selftest_clobber_d29;
case 30: return selftest_clobber_d30;
case 31: return selftest_clobber_d31;
default: return NULL;
}
}
static noop_func *get_clobber_v_upper(int reg)
{
if (!(checkasm_get_cpu_flags() & SELFTEST_CPU_FLAG_AARCH64))
return NULL;
switch (reg) {
case 8: return selftest_clobber_v8_upper;
case 9: return selftest_clobber_v9_upper;
case 10: return selftest_clobber_v10_upper;
case 11: return selftest_clobber_v11_upper;
case 12: return selftest_clobber_v12_upper;
case 13: return selftest_clobber_v13_upper;
case 14: return selftest_clobber_v14_upper;
case 15: return selftest_clobber_v15_upper;
default: return NULL;
}
}
static many_args_func *get_check_clobber_upper(int arg)
{
if (!(checkasm_get_cpu_flags() & SELFTEST_CPU_FLAG_AARCH64))
return NULL;
switch (arg) {
case 0: return selftest_check_clobber_upper_x0_aarch64;
case 1: return selftest_check_clobber_upper_x1_aarch64;
case 2: return selftest_check_clobber_upper_x2_aarch64;
case 3: return selftest_check_clobber_upper_x3_aarch64;
case 4: return selftest_check_clobber_upper_x4_aarch64;
case 5: return selftest_check_clobber_upper_x5_aarch64;
case 6: return selftest_check_clobber_upper_x6_aarch64;
case 7: return selftest_check_clobber_upper_x7_aarch64;
case 8: return selftest_check_clobber_upper_stack0_aarch64;
case 9: return selftest_check_clobber_upper_stack1_aarch64;
case 10: return selftest_check_clobber_upper_stack2_aarch64;
default: return NULL;
}
}
DEF_NOOP_GETTER(SELFTEST_CPU_FLAG_AARCH64, sigill_aarch64)
DEF_MANY_ARGS_GETTER(SELFTEST_CPU_FLAG_AARCH64, clobber_stack_args_aarch64)
DEF_MANY_ARGS_GETTER(SELFTEST_CPU_FLAG_AARCH64, clobber_stack_aarch64)
DEF_MAX_INT_ARGS_GETTER(SELFTEST_CPU_FLAG_AARCH64, check_max_int_args_aarch64)
DEF_MAX_INT64_ARGS_GETTER(SELFTEST_CPU_FLAG_AARCH64, check_max_int64_args_aarch64)
static void check_clobber_x(int from, int to)
{
checkasm_declare(void, int);
for (int reg = from; reg < to; reg++) {
noop_func *clobber = get_clobber_x(reg);
if (!clobber)
break;
if (checkasm_check_func(clobber, "clobber_x%d", reg)) {
checkasm_call_new(0);
}
}
checkasm_report("clobber_x");
}
static void check_clobber_d(int from, int to)
{
checkasm_declare(void, int);
for (int reg = from; reg < to; reg++) {
noop_func *clobber = get_clobber_d(reg);
if (!clobber)
break;
if (checkasm_check_func(clobber, "clobber_d%d", reg)) {
checkasm_call_new(0);
}
}
checkasm_report("clobber_d");
}
static void check_clobber_v_upper(int from, int to)
{
checkasm_declare(void, int);
for (int reg = from; reg < to; reg++) {
noop_func *clobber = get_clobber_v_upper(reg);
if (!clobber)
break;
if (checkasm_check_func(clobber, "clobber_v%d_upper", reg)) {
checkasm_call_new(0);
}
}
checkasm_report("clobber_v_upper");
}
static void check_clobber_arg_upper(void)
{
checkasm_declare(void, int, int, int, int, int, int, int, int, int, int, int);
for (int arg = 0; arg < 11; arg++) {
many_args_func *check_clobber = get_check_clobber_upper(arg);
if (!check_clobber)
break;
if (checkasm_check_func(check_clobber, "check_clobber_arg_upper_%d", arg)) {
checkasm_call_new(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11);
}
}
checkasm_report("check_clobber_arg_upper");
}
static void selftest_test_many_args(many_args_func fun, const char *name)
{
checkasm_declare(void, int, int, int, int, int, int, int, int, int, int, int);
if (checkasm_check_func(fun, "%s", name)) {
/* don't call unchecked because that one is called without wrapping,
* and we try to cloober the stack here. */
(void) func_ref;
checkasm_call_new(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11);
}
checkasm_report("%s", name);
}
static void selftest_test_max_int_args(max_int_args_func fun, const char *name)
{
checkasm_declare(void, int, int, int, int, int, int, int, int, int, int, int, int,
int, int, int);
if (checkasm_check_func(fun, "%s", name)) {
(void) func_ref;
checkasm_call_new(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15);
}
checkasm_report("%s", name);
}
static void selftest_test_max_int64_args(max_int64_args_func fun, const char *name)
{
checkasm_declare(void, int64_t, int64_t, int64_t, int64_t, int64_t, int64_t, int64_t,
int64_t, int64_t, int64_t, int64_t, int64_t, int64_t, int64_t,
int64_t);
if (checkasm_check_func(fun, "%s", name)) {
(void) func_ref;
checkasm_call_new(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15);
}
checkasm_report("%s", name);
}
void selftest_check_aarch64(void)
{
check_clobber_x(0, 18);
// Don't try testing x18 - it has platform specific behaviour.
check_clobber_d(0, 8);
check_clobber_d(16, 32);
check_clobber_v_upper(8, 16);
selftest_test_many_args(get_clobber_stack_args_aarch64(), "clobber_stack_args");
selftest_test_max_int_args(get_check_max_int_args_aarch64(), "check_max_int_args");
selftest_test_max_int64_args(get_check_max_int64_args_aarch64(),
"check_max_int64_args");
check_clobber_arg_upper();
if (!checkasm_should_fail(SELFTEST_CPU_FLAG_AARCH64))
return;
selftest_test_noop(get_sigill_aarch64(), "sigill");
selftest_test_many_args(get_clobber_stack_aarch64(), "clobber_stack");
check_clobber_x(19, 30);
check_clobber_d(8, 16);
}
#endif

View File

@@ -1,247 +0,0 @@
/******************************************************************************
* Copyright © 2025 Martin Storsjo
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
#define PRIVATE_PREFIX selftest_
#include "src/checkasm_config.h"
#if ARCH_AARCH64
#include "src/arm/asm.S"
function sigill_aarch64, export=1
.inst 0 // udf #0
ret
endfunc
.macro clobber_x reg
function clobber_\reg, export=1
mov \reg, #0x42
ret
endfunc
.endm
.macro clobber_d reg
function clobber_d\reg, export=1
movi v\reg\().8b, #0x42
ret
endfunc
.endm
.macro clobber_v_upper reg
function clobber_v\reg\()_upper, export=1
str d\reg, [sp, #-16]!
movi v\reg\().16b, #0x42
ldr d\reg, [sp], #16
ret
endfunc
.endm
.irp r, x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17
clobber_x \r
.endr
// x18 skipped
.irp r, x19, x20, x21, x22, x23, x24, x25, x26, x27, x28, x29
clobber_x \r
.endr
.irp r, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31
clobber_d \r
.endr
.irp r, 8, 9, 10, 11, 12, 13, 14, 15
clobber_v_upper \r
.endr
// void clobber_stack_args_aarch64(int, int, int, int, int, int, int, int, int, int, int);
//
// Write into the stack of the caller, overwriting the arguments that are on
// the stack. This is legal to do.
function clobber_stack_args_aarch64, export=1
mov w0, #0x42
#ifdef __APPLE__
str w0, [sp, #0]
str w0, [sp, #4]
str w0, [sp, #8]
#else
str w0, [sp, #0]
str w0, [sp, #8]
str w0, [sp, #16]
#endif
ret
endfunc
// void clobber_stack_aarch64(int, int, int, int, int, int, int, int, int, int, int);
//
// Write into the stack of the caller, writing past the arguments on the stack
// that we are allowed to touch.
function clobber_stack_aarch64, export=1
mov w0, #0x42
#ifdef __APPLE__
str w0, [sp, #0]
str w0, [sp, #4]
str w0, [sp, #8]
// This write on its own falls within the 8 byte stack slot of
// the third stack argument (above), which we are allowed to clobber,
// so the wrapper won't notice it; we need to write past it for it to
// be noticed.
str w0, [sp, #12]
// Write past the 8 byte stack slot of the third/fourth argument, to
// allow checked_call to catch it.
str w0, [sp, #16]
#else
str w0, [sp, #0]
str w0, [sp, #8]
str w0, [sp, #16]
str w0, [sp, #24]
#endif
ret
endfunc
// void check_max_int_args_aarch64(int, int, int, int, int, int, int, int, int, int, int, int, int, int, int);
function check_max_int_args_aarch64, export=1
#ifdef __APPLE__
ldr w8, [sp, #0]
ldr w9, [sp, #4]
ldr w10, [sp, #8]
ldr w11, [sp, #12]
ldr w12, [sp, #16]
ldr w13, [sp, #20]
ldr w14, [sp, #24]
#else
ldr w8, [sp, #0]
ldr w9, [sp, #8]
ldr w10, [sp, #16]
ldr w11, [sp, #24]
ldr w12, [sp, #32]
ldr w13, [sp, #40]
ldr w14, [sp, #48]
#endif
.macro check_reg reg, val
cmp \reg, #\val
bne 9f
.endm
check_reg w0, 1
check_reg w1, 2
check_reg w2, 3
check_reg w3, 4
check_reg w4, 5
check_reg w5, 6
check_reg w6, 7
check_reg w7, 8
check_reg w8, 9
check_reg w9, 10
check_reg w10, 11
check_reg w11, 12
check_reg w12, 13
check_reg w13, 14
check_reg w14, 15
ret
9:
.inst 0 // udf #0
ret
.purgem check_reg
endfunc
// void check_max_int64_args_aarch64(int64_t, int64_t, int64_t, int64_t, int64_t, int64_t, int64_t, int64_t, int64_t, int64_t, int64_t, int64_t, int64_t, int64_t, int64_t);
function check_max_int64_args_aarch64, export=1
ldr x8, [sp, #0]
ldr x9, [sp, #8]
ldr x10, [sp, #16]
ldr x11, [sp, #24]
ldr x12, [sp, #32]
ldr x13, [sp, #40]
ldr x14, [sp, #48]
.macro check_reg reg, val
cmp \reg, #\val
bne 9f
.endm
check_reg x0, 1
check_reg x1, 2
check_reg x2, 3
check_reg x3, 4
check_reg x4, 5
check_reg x5, 6
check_reg x6, 7
check_reg x7, 8
check_reg x8, 9
check_reg x9, 10
check_reg x10, 11
check_reg x11, 12
check_reg x12, 13
check_reg x13, 14
check_reg x14, 15
ret
9:
.inst 0 // udf #0
ret
.purgem check_reg
endfunc
// void check_clobber_upper_aarch64(int, int, int, int, int, int, int, int, int, int, int);
//
// Check that the upper half of parameters that are passed as int actually
// are filled with garbage.
//
// This test has a bit of an inverted role; this test tries to verify the
// actions of the checked_call wrapper. If it finds a fault, it triggers
// a crash, to indicate a fault.
.macro check_clobber_reg reg
function check_clobber_upper_\reg\()_aarch64, export=1
lsr \reg, \reg, #32
cbnz \reg, 9f
.inst 0 // udf #0
9:
ret
endfunc
.endm
.macro check_clobber_stack arg
function check_clobber_upper_stack\arg\()_aarch64, export=1
#ifndef __APPLE__
// This assumes the stack argument layout that isn't used on Apple
// platforms. On Apple platforms, 32 bit arguments are packed tightly,
// on the stack, so there's nothing that can be clobbered - just
// return success.
ldr x0, [sp, #8*\arg]
lsr x0, x0, #32
cbnz x0, 9f
.inst 0 // udf #0
9:
#endif
ret
endfunc
.endm
.irpc r, 01234567
check_clobber_reg x\r
.endr
.irpc r, 012
check_clobber_stack \r
.endr
#endif

View File

@@ -1,319 +0,0 @@
/*
* Copyright © 2024, Marvin Scholz
* Copyright © 2019, VideoLAN and dav1d authors
* Copyright © 2019, Two Orioles, LLC
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <float.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "tests.h"
void selftest_test_copy(copy_func fun, const char *name, const int min_width)
{
#define WIDTH 256
BUF_RECT(uint8_t, c_dst, WIDTH, 1);
BUF_RECT(uint8_t, a_dst, WIDTH, 1);
CHECKASM_ALIGN(uint8_t src[WIDTH]);
INITIALIZE_BUF(src);
checkasm_declare(void, uint8_t *dest, const uint8_t *src, size_t n);
for (int w = min_width; w <= WIDTH; w *= 2) {
if (checkasm_check_func(fun, "%s_%d", name, w)) {
CLEAR_BUF_RECT(c_dst);
CLEAR_BUF_RECT(a_dst);
/* Make sure that the destination buffer actually differs from
* the source buffer, to make sure that a skipped write does
* trigger a failure. */
for (int i = 0; i < w; i++)
c_dst[i] = a_dst[i] = ~src[i];
checkasm_call_ref(c_dst, src, w);
checkasm_call_new(a_dst, src, w);
/* Test all checkasm_check() variants */
checkasm_check1d(uint8_t, c_dst, a_dst, w, "1d");
checkasm_check2d(uint8_t, c_dst, 0, a_dst, 0, w, 1, "2d");
checkasm_check_rect(c_dst, c_dst_stride, a_dst, a_dst_stride, w, 1, "rect");
checkasm_check1d_padded(uint8_t, c_dst, a_dst, w, "1d_padded", 1, 64);
checkasm_check2d_padded(uint8_t, c_dst, 0, a_dst, 0, w, 1,
"2d_padded", 1, 1, 16);
checkasm_check_rect_padded(c_dst, c_dst_stride, a_dst, a_dst_stride, w, 1,
"rect_padded");
checkasm_check_rect_padded_align(c_dst, c_dst_stride, a_dst, a_dst_stride, w, 1,
"rect_align", 1, 1);
checkasm_bench_new(a_dst, src, w);
}
}
checkasm_report("%s", name);
#undef WIDTH
}
void selftest_test_noop(noop_func fun, const char *name)
{
checkasm_declare(void, int);
if (checkasm_check_func(fun, "%s", name)) {
/* don't call unchecked because some of these functions are designed to
* e.g. intentionally corrupt the stack */
(void) checkasm_func_ref;
checkasm_call_new(0);
}
checkasm_report("%s", name);
}
void selftest_test_float(float_func fun, const char *name, const float input)
{
checkasm_declare(float, float);
if (checkasm_check_func(fun, "%s", name)) {
float x = checkasm_call_ref(input);
float y = checkasm_call_new(input);
if (!checkasm_float_near_abs_eps(x, y, FLT_EPSILON)) {
if (checkasm_fail())
fprintf(stderr, "expected %f, got %f\n", x, y);
}
}
checkasm_report("%s", name);
}
static void selftest_test_double(double_func fun, const char *name,
const double input)
{
checkasm_declare(double, double);
if (checkasm_check_func(fun, "%s", name)) {
double x = checkasm_call_ref(input);
double y = checkasm_call_new(input);
if (!checkasm_double_near_abs_eps(x, y, DBL_EPSILON)) {
if (checkasm_fail())
fprintf(stderr, "expected %f, got %f\n", x, y);
}
}
checkasm_report("%s", name);
}
static DEF_COPY_FUNC(overwrite_left)
{
memcpy(dst, src, size);
dst[-1] = dst[-2] = dst[-3] = dst[-4] = 0xAC;
}
static DEF_COPY_FUNC(overwrite_right)
{
memcpy(dst, src, size);
dst[size] = dst[size + 1] = dst[size + 2] = dst[size + 3] = 0xAC;
}
static DEF_COPY_FUNC(underwrite)
{
if (size < 4)
return;
memcpy(dst, src, size - 4);
}
static DEF_NOOP_FUNC(segfault)
{
volatile int *bad = NULL;
*bad = 0;
}
static DEF_FLOAT_FUNC(sqrt)
{
return sqrtf(input);
}
static int identity_ref(const int x)
{
return x;
}
/* Just make this a separate function to test the checked wrappers */
static int identity_new(const int x)
{
return x;
}
static void selftest_test_retval(void)
{
const uint64_t flags = checkasm_get_cpu_flags();
checkasm_declare(int, int);
if (checkasm_check_func(flags ? identity_new : identity_ref, "identity")) {
for (int i = 0; i < 10; i++) {
int x = checkasm_call_ref(i);
int y = checkasm_call_new(i);
if (x != y) {
if (checkasm_fail())
fprintf(stderr, "expected %d, got %d\n", x, y);
}
}
}
checkasm_report("identity");
}
static int truncate_c(const float x)
{
return (int) x;
}
static void selftest_test_float_arg(void)
{
checkasm_declare(int, float);
if (checkasm_check_func(truncate_c, "truncate")) {
for (float f = 0.0f; f <= 10.0f; f += 0.5f) {
int x = checkasm_call_ref(f);
int y = checkasm_call_new(f);
if (x != y) {
if (checkasm_fail())
fprintf(stderr, "expected %d, got %d\n", x, y);
}
}
}
checkasm_report("truncate");
}
static void selftest_test_double_arg(void)
{
checkasm_declare(long, double);
if (checkasm_check_func(lrint, "lrint")) {
for (float f = 0.0f; f <= 10.0f; f += 0.5f) {
long x = checkasm_call_ref(f);
long y = checkasm_call_new(f);
if (x != y) {
if (checkasm_fail())
fprintf(stderr, "expected %ld, got %ld\n", x, y);
}
}
}
checkasm_report("lrint");
}
DEF_COPY_GETTER(SELFTEST_CPU_FLAG_BAD_C, overwrite_left)
DEF_COPY_GETTER(SELFTEST_CPU_FLAG_BAD_C, overwrite_right)
DEF_COPY_GETTER(SELFTEST_CPU_FLAG_BAD_C, underwrite)
DEF_NOOP_GETTER(SELFTEST_CPU_FLAG_BAD_C, segfault)
/* Ensure we can call declare_func() inside check_func() */
static void selftest_test_check_declare(void)
{
/* Pick a function that will actually crash, to ensure the error
* handling still works in this case */
noop_func *func = get_segfault();
if (checkasm_check_func(func, "check_declare")) {
checkasm_declare(void, int);
checkasm_call_ref(0);
checkasm_call_new(0);
}
checkasm_report("check_declare");
}
typedef int (int_func)(int);
static int wrapper(int_func *func, int arg)
{
return func(arg);
}
static void selftest_test_wrappers(void)
{
if (checkasm_check_func(identity_ref, "override_funcs")) {
checkasm_declare(int, int);
int x = checkasm_call(identity_ref, 12345);
int y = checkasm_call_checked(identity_new, 12345);
if (x != y)
checkasm_fail();
}
if (checkasm_check_func(identity_ref, "wrapper_func")) {
checkasm_declare(int, int_func *, int); // type of wrapper
int x = checkasm_call(wrapper, (int_func *) checkasm_key_ref, 12345);
int y = checkasm_call_checked(wrapper, (int_func *) checkasm_key_new, 12345);
if (x != y)
checkasm_fail();
}
checkasm_report("wrappers");
}
static void selftest_test_variants(void)
{
for (int i = 0; i < 2; i++) {
checkasm_set_func_variant(i ? "new" : "ref");
if (checkasm_check_func(i ? identity_new : identity_ref, "func_id")) {
checkasm_declare(int, int);
int x = checkasm_call_ref(12345);
int y = checkasm_call_new(12345);
if (x != y)
checkasm_fail();
checkasm_bench_new(12345);
}
}
checkasm_report("func_id");
}
void selftest_check_generic(void)
{
selftest_test_copy(selftest_copy_c, "copy_generic", 1);
selftest_test_float(selftest_sqrt, "sqrt_generic", 2.0f);
selftest_test_float_arg();
selftest_test_double(sqrt, "sqrt", 2.);
selftest_test_double_arg();
selftest_test_retval();
selftest_test_wrappers();
selftest_test_variants();
if (!checkasm_should_fail(SELFTEST_CPU_FLAG_BAD_C))
return;
selftest_test_copy(get_overwrite_left(), "overwrite_left", 1);
selftest_test_copy(get_overwrite_right(), "overwrite_right", 1);
selftest_test_copy(get_underwrite(), "underwrite", 1);
selftest_test_noop(get_segfault(), "segfault");
selftest_test_check_declare();
}

View File

@@ -1,93 +0,0 @@
# Copyright © 2025 Niklas Haas
# Copyright © 2024, Marvin Scholz
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
if build_tests.disabled()
subdir_done()
endif
libm_dependency = cc.find_library('m', required: false)
optional_arguments = []
if cc.get_argument_syntax() != 'msvc'
optional_arguments += [
'-Wno-unused-parameter',
]
endif
extra_arguments = cc.get_supported_arguments(optional_arguments)
checkasm_asm_objs = []
checkasm_asm_sources = []
checkasm_test_sources = files(
'selftest.c',
'generic.c',
'arm/32/tests.c',
'arm/64/tests.c',
'riscv/tests.c',
'x86/tests.c',
)
if arch_x86
checkasm_test_sources += files(
'x86/tests_asm.asm',
)
elif arch_riscv
checkasm_test_sources += files(
'riscv/tests_asm.S',
)
elif arch_aarch64
checkasm_asm_sources = files('arm/64/tests_asm.S')
elif arch_arm
checkasm_asm_sources = files('arm/32/tests_asm.S')
else
warning('There are no assembly functions to test on the current platform.')
endif
if use_gaspp and checkasm_asm_sources.length() > 0
gaspp_args = [
'-I@0@'.format(meson.project_source_root() + '/include'),
'-I@0@'.format(meson.project_source_root()),
'-I@0@'.format(meson.project_build_root()),
'-I@0@'.format(meson.project_build_root() + '/src'),
'-DCHECKASM_HAVE_GENERATED_H',
]
checkasm_asm_objs += gaspp_gen.process(checkasm_asm_sources, extra_args: gaspp_args)
else
checkasm_test_sources += checkasm_asm_sources
endif
checkasm_test = executable('checkasm-selftest',
checkasm_test_sources,
checkasm_asm_objs,
checkasm_config,
c_args: [extra_arguments, '-DCHECKASM_HAVE_GENERATED_H'],
nasm_args: '-DCHECKASM_HAVE_GENERATED_H',
dependencies: [checkasm_dep, libm_dependency],
include_directories: include_directories('../src'),
build_by_default: false,
install: false,
)
test('selftest', checkasm_test, suite: 'checkasm', args: ['--verbose'])
benchmark('selftest', checkasm_test, suite: 'checkasm', args: ['--bench', '--verbose'])

View File

@@ -1,222 +0,0 @@
#include "tests.h"
#if ARCH_RISCV
/* Re-use helpers from main checkasm library */
#include "src/cpu.h"
uint64_t selftest_get_cpu_flags_riscv(void)
{
uint64_t flags = SELFTEST_CPU_FLAG_RVI;
if (checkasm_has_float())
flags |= SELFTEST_CPU_FLAG_RVF;
if (checkasm_has_vector())
flags |= SELFTEST_CPU_FLAG_RVV;
return flags;
}
DEF_COPY_FUNC(copy_rvv);
DEF_COPY_GETTER(SELFTEST_CPU_FLAG_RVV, copy_rvv)
DEF_NOOP_FUNC(clobber_ra);
DEF_NOOP_FUNC(clobber_sp);
DEF_NOOP_FUNC(clobber_gp);
DEF_NOOP_FUNC(clobber_t0);
DEF_NOOP_FUNC(clobber_t1);
DEF_NOOP_FUNC(clobber_t2);
DEF_NOOP_FUNC(clobber_s0);
DEF_NOOP_FUNC(clobber_s1);
DEF_NOOP_FUNC(clobber_a0);
DEF_NOOP_FUNC(clobber_a1);
DEF_NOOP_FUNC(clobber_a2);
DEF_NOOP_FUNC(clobber_a3);
DEF_NOOP_FUNC(clobber_a4);
DEF_NOOP_FUNC(clobber_a5);
DEF_NOOP_FUNC(clobber_a6);
DEF_NOOP_FUNC(clobber_a7);
DEF_NOOP_FUNC(clobber_s2);
DEF_NOOP_FUNC(clobber_s3);
DEF_NOOP_FUNC(clobber_s4);
DEF_NOOP_FUNC(clobber_s5);
DEF_NOOP_FUNC(clobber_s6);
DEF_NOOP_FUNC(clobber_s7);
DEF_NOOP_FUNC(clobber_s8);
DEF_NOOP_FUNC(clobber_s9);
DEF_NOOP_FUNC(clobber_s10);
DEF_NOOP_FUNC(clobber_s11);
DEF_NOOP_FUNC(clobber_t3);
DEF_NOOP_FUNC(clobber_t4);
DEF_NOOP_FUNC(clobber_t5);
DEF_NOOP_FUNC(clobber_t6);
DEF_NOOP_FUNC(clobber_ft0);
DEF_NOOP_FUNC(clobber_ft1);
DEF_NOOP_FUNC(clobber_ft2);
DEF_NOOP_FUNC(clobber_ft3);
DEF_NOOP_FUNC(clobber_ft4);
DEF_NOOP_FUNC(clobber_ft5);
DEF_NOOP_FUNC(clobber_ft6);
DEF_NOOP_FUNC(clobber_ft7);
DEF_NOOP_FUNC(clobber_fs0);
DEF_NOOP_FUNC(clobber_fs1);
DEF_NOOP_FUNC(clobber_fa0);
DEF_NOOP_FUNC(clobber_fa1);
DEF_NOOP_FUNC(clobber_fa2);
DEF_NOOP_FUNC(clobber_fa3);
DEF_NOOP_FUNC(clobber_fa4);
DEF_NOOP_FUNC(clobber_fa5);
DEF_NOOP_FUNC(clobber_fa6);
DEF_NOOP_FUNC(clobber_fa7);
DEF_NOOP_FUNC(clobber_fs2);
DEF_NOOP_FUNC(clobber_fs3);
DEF_NOOP_FUNC(clobber_fs4);
DEF_NOOP_FUNC(clobber_fs5);
DEF_NOOP_FUNC(clobber_fs6);
DEF_NOOP_FUNC(clobber_fs7);
DEF_NOOP_FUNC(clobber_fs8);
DEF_NOOP_FUNC(clobber_fs9);
DEF_NOOP_FUNC(clobber_fs10);
DEF_NOOP_FUNC(clobber_fs11);
DEF_NOOP_FUNC(clobber_ft8);
DEF_NOOP_FUNC(clobber_ft9);
DEF_NOOP_FUNC(clobber_ft10);
DEF_NOOP_FUNC(clobber_ft11);
DEF_NOOP_FUNC(sigill_riscv);
DEF_NOOP_FUNC(corrupt_stack_riscv);
DEF_NOOP_GETTER(SELFTEST_CPU_FLAG_RVI, sigill_riscv)
DEF_NOOP_GETTER(SELFTEST_CPU_FLAG_RVI, corrupt_stack_riscv)
typedef struct RiscvRegister {
const char *name;
noop_func *clobber;
} RiscvRegister;
static const RiscvRegister registers_safe[] = {
{ "ra", selftest_clobber_ra },
{ "t0", selftest_clobber_t0 },
{ "t1", selftest_clobber_t1 },
{ "t2", selftest_clobber_t2 },
{ "t3", selftest_clobber_t3 },
{ "t4", selftest_clobber_t4 },
{ "t5", selftest_clobber_t5 },
{ "t6", selftest_clobber_t6 },
{ "a0", selftest_clobber_a0 },
{ "a1", selftest_clobber_a1 },
{ "a2", selftest_clobber_a2 },
{ "a3", selftest_clobber_a3 },
{ "a4", selftest_clobber_a4 },
{ "a5", selftest_clobber_a5 },
{ "a6", selftest_clobber_a6 },
{ "a7", selftest_clobber_a7 },
{ NULL, NULL }
};
static const RiscvRegister float_registers_safe[] = {
{ "ft0", selftest_clobber_ft0 },
{ "ft1", selftest_clobber_ft1 },
{ "ft2", selftest_clobber_ft2 },
{ "ft3", selftest_clobber_ft3 },
{ "ft4", selftest_clobber_ft4 },
{ "ft5", selftest_clobber_ft5 },
{ "ft6", selftest_clobber_ft6 },
{ "ft7", selftest_clobber_ft7 },
#ifdef __riscv_float_abi_soft
{ "fs0", selftest_clobber_fs0 },
{ "fs1", selftest_clobber_fs1 },
#endif
{ "fa0", selftest_clobber_fa0 },
{ "fa1", selftest_clobber_fa1 },
{ "fa2", selftest_clobber_fa2 },
{ "fa3", selftest_clobber_fa3 },
{ "fa4", selftest_clobber_fa4 },
{ "fa5", selftest_clobber_fa5 },
{ "fa6", selftest_clobber_fa6 },
{ "fa7", selftest_clobber_fa7 },
#ifdef __riscv_float_abi_soft
{ "fs2", selftest_clobber_fs2 },
{ "fs3", selftest_clobber_fs3 },
{ "fs4", selftest_clobber_fs4 },
{ "fs5", selftest_clobber_fs5 },
{ "fs6", selftest_clobber_fs6 },
{ "fs7", selftest_clobber_fs7 },
{ "fs8", selftest_clobber_fs8 },
{ "fs9", selftest_clobber_fs9 },
{ "fs10", selftest_clobber_fs10 },
{ "fs11", selftest_clobber_fs11 },
#endif
{ "ft8", selftest_clobber_ft8 },
{ "ft9", selftest_clobber_ft9 },
{ "ft10", selftest_clobber_ft10 },
{ "ft11", selftest_clobber_ft11 },
{ NULL, NULL },
};
static const RiscvRegister registers_unsafe[] = {
{ "s0", selftest_clobber_s0 },
{ "s1", selftest_clobber_s1 },
{ "s2", selftest_clobber_s2 },
{ "s3", selftest_clobber_s3 },
{ "s4", selftest_clobber_s4 },
{ "s5", selftest_clobber_s5 },
{ "s6", selftest_clobber_s6 },
{ "s7", selftest_clobber_s7 },
{ "s8", selftest_clobber_s8 },
{ "s9", selftest_clobber_s9 },
{ "s10", selftest_clobber_s10 },
{ "s11", selftest_clobber_s11 },
{ "sp", selftest_clobber_sp },
{ "gp", selftest_clobber_gp },
/* Can't clobber tp because checkasm.S saves registers in TLS */
{ NULL, NULL }
};
static const RiscvRegister float_registers_unsafe[] = {
#ifndef __riscv_float_abi_soft
{ "fs0", selftest_clobber_fs0 },
{ "fs1", selftest_clobber_fs1 },
{ "fs2", selftest_clobber_fs2 },
{ "fs3", selftest_clobber_fs3 },
{ "fs4", selftest_clobber_fs4 },
{ "fs5", selftest_clobber_fs5 },
{ "fs6", selftest_clobber_fs6 },
{ "fs7", selftest_clobber_fs7 },
{ "fs8", selftest_clobber_fs8 },
{ "fs9", selftest_clobber_fs9 },
{ "fs10", selftest_clobber_fs10 },
{ "fs11", selftest_clobber_fs11 },
#endif
{ NULL, NULL }
};
static void check_clobber(uint64_t mask, unsigned char letter,
const RiscvRegister *registers)
{
const uint64_t flag = checkasm_get_cpu_flags() & mask;
checkasm_declare(void, int);
for (int i = 0; registers[i].name; i++) {
noop_func *const func = flag ? registers[i].clobber : NULL;
if (checkasm_check_func(func, "clobber_%s", registers[i].name)) {
checkasm_call_new(0);
}
}
checkasm_report("clobber_%c", letter);
}
void selftest_check_riscv(void)
{
selftest_test_copy(get_copy_rvv(), "copy_rvv", 1);
check_clobber(SELFTEST_CPU_FLAG_RVI, 'x', registers_safe);
check_clobber(SELFTEST_CPU_FLAG_RVF, 'f', float_registers_safe);
if (!checkasm_should_fail(1))
return;
selftest_test_noop(get_sigill_riscv(), "sigill");
selftest_test_noop(get_corrupt_stack_riscv(), "corrupt_stack");
check_clobber(SELFTEST_CPU_FLAG_RVI, 'x', registers_unsafe);
check_clobber(SELFTEST_CPU_FLAG_RVF, 'f', float_registers_unsafe);
}
#endif

View File

@@ -1,96 +0,0 @@
/******************************************************************************
* Copyright © 2018, VideoLAN and dav1d authors
* Copyright © 2023, Nathan Egge
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
#define PRIVATE_PREFIX selftest_
#include "src/checkasm_config.h"
#if ARCH_RISCV
#include "src/riscv/asm.S"
function copy_rvv, export=1, ext=zve32x
vsetvli t0, a2, e8, m8, ta, ma
vle8.v v0, (a1)
add a1, a1, t0
sub a2, a2, t0
vse8.v v0, (a0)
add a0, a0, t0
bnez a2, copy_rvv
ret
endfunc
.macro clobber_fn reg
function clobber_\reg, export=1
not \reg, \reg
ret
endfunc
.endm
.irp reg, sp, gp, t0, t1, t2, s0, s1, a0, a1, a2, a3, a4, a5, a6, a7, \
s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, t3, t4, t5, t6
clobber_fn \reg
.endr
function clobber_ra, export=1
mv t0, ra
not ra, ra
jr t0
endfunc
.macro clobber_f_fn reg
function clobber_\reg, export=1, ext=f
fmv.x.w t0, \reg
not t0, t0
fmv.w.x \reg, zero
ret
endfunc
.endm
.irp reg, ft0, ft1, ft2, ft3, ft4, ft5, ft6, ft7, fs0, fs1, \
fa0, fa1, fa2, fa3, fa4, fa5, fa6, fa7, fs2, fs3, \
fs4, fs5, fs6, fs7, fs8, fs9, fs10, fs11, ft8, ft9, ft10, ft11
clobber_f_fn \reg
.endr
function sigill_riscv, export=1
.word 0
endfunc
function corrupt_stack_riscv, export=1
mv t0, sp
li t1, (__riscv_xlen / 2)
1:
addi t1, t1, -1
sw zero, (t0)
addi t0, t0, 4
bnez t1, 1b
ret
endfunc
#endif

View File

@@ -1,62 +0,0 @@
#include "tests.h"
#include <checkasm/checkasm.h>
static const CheckasmCpuInfo cpus[] = {
{ "Bad C", "badc", SELFTEST_CPU_FLAG_BAD_C },
#if ARCH_X86
{ "Generic x86", "x86", SELFTEST_CPU_FLAG_X86 },
{ "MMX", "mmx", SELFTEST_CPU_FLAG_MMX },
{ "SSE2", "sse2", SELFTEST_CPU_FLAG_SSE2 },
{ "AVX-2", "avx2", SELFTEST_CPU_FLAG_AVX2 },
{ "AVX-512", "avx512", SELFTEST_CPU_FLAG_AVX512 },
#endif
#if ARCH_RISCV
{ "Generic RISC-V", "rvi", SELFTEST_CPU_FLAG_RVI },
{ "Floating point", "rvf", SELFTEST_CPU_FLAG_RVF },
{ "Vector", "rvv", SELFTEST_CPU_FLAG_RVV },
#endif
#if ARCH_AARCH64
{ "Generic aarch64", "aarch64", SELFTEST_CPU_FLAG_AARCH64 },
#endif
#if ARCH_ARM
{ "Generic ARM", "arm", SELFTEST_CPU_FLAG_ARM },
{ "VFP", "vfp", SELFTEST_CPU_FLAG_VFP },
{ "VFP D32", "vfpd32", SELFTEST_CPU_FLAG_VFPD32 },
#endif
{0}
};
static const CheckasmTest tests[] = {
{ "generic", selftest_check_generic },
#if ARCH_X86
{ "x86", selftest_check_x86 },
#elif ARCH_RISCV
{ "riscv", selftest_check_riscv },
#elif ARCH_AARCH64
{ "aarch64", selftest_check_aarch64 },
#elif ARCH_ARM
{ "arm", selftest_check_arm },
#endif
{0}
};
int main(int argc, const char *argv[])
{
CheckasmConfig cfg = {
.cpu_flags = cpus,
.tests = tests,
.cpu = SELFTEST_CPU_FLAG_BAD_C,
};
#if ARCH_X86
cfg.cpu |= selftest_get_cpu_flags_x86();
#elif ARCH_RISCV
cfg.cpu |= selftest_get_cpu_flags_riscv();
#elif ARCH_AARCH64
cfg.cpu |= selftest_get_cpu_flags_aarch64();
#elif ARCH_ARM
cfg.cpu |= selftest_get_cpu_flags_arm();
#endif
return checkasm_main(&cfg, argc, argv);
}

View File

@@ -1,114 +0,0 @@
/*
* Copyright © 2025, Niklas Haas
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef CHECKASM_TESTS_H
#define CHECKASM_TESTS_H
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#include "src/checkasm_config.h"
#include <checkasm/test.h>
#include <checkasm/utils.h>
enum {
SELFTEST_CPU_FLAG_BAD_C = 1 << 0, // dummy flag for "bad" C implementations
#if ARCH_X86
SELFTEST_CPU_FLAG_X86 = 1 << 1,
SELFTEST_CPU_FLAG_MMX = 1 << 2,
SELFTEST_CPU_FLAG_SSE2 = 1 << 3,
SELFTEST_CPU_FLAG_AVX2 = 1 << 4,
SELFTEST_CPU_FLAG_AVX512 = 1 << 5,
#elif ARCH_RISCV
SELFTEST_CPU_FLAG_RVI = 1 << 1,
SELFTEST_CPU_FLAG_RVF = 1 << 2,
SELFTEST_CPU_FLAG_RVV = 1 << 3,
#elif ARCH_AARCH64
SELFTEST_CPU_FLAG_AARCH64 = 1 << 1,
#elif ARCH_ARM
SELFTEST_CPU_FLAG_ARM = 1 << 1,
SELFTEST_CPU_FLAG_VFP = 1 << 2,
SELFTEST_CPU_FLAG_VFPD32 = 1 << 3,
#endif
};
#define DEF_GETTER(FLAG, NAME, func_type, fallback) \
static func_type *get_##NAME(void) \
{ \
return (checkasm_get_cpu_flags() & FLAG) ? selftest_##NAME : fallback; \
}
/* Should return the arch-specific flags */
uint64_t selftest_get_cpu_flags_x86(void);
uint64_t selftest_get_cpu_flags_riscv(void);
uint64_t selftest_get_cpu_flags_aarch64(void);
uint64_t selftest_get_cpu_flags_arm(void);
/**
* Copy `size` (power-of-two) bytes from aligned buffers `src` to `dst`.
*/
typedef void(copy_func)(uint8_t *dst, const uint8_t *src, size_t size);
void selftest_test_copy(copy_func *func, const char *name, int min_width);
#define DEF_COPY_FUNC(NAME) \
void selftest_##NAME(uint8_t *dst, const uint8_t *src, size_t size)
#define DEF_COPY_GETTER(FLAG, NAME) DEF_GETTER(FLAG, NAME, copy_func, selftest_copy_c)
/* Reference function for copy routines */
static inline DEF_COPY_FUNC(copy_c)
{
memcpy(dst, src, size);
}
/**
* Do nothing. Used to test side effects, stack corruption etc.
* The singular int parameter is just to have at least one parameter,
* which is required by `declare_func`.
*/
typedef void(noop_func)(int unused);
void selftest_test_noop(noop_func *func, const char *name);
#define DEF_NOOP_FUNC(NAME) void selftest_##NAME(int unused)
#define DEF_NOOP_GETTER(FLAG, NAME) DEF_GETTER(FLAG, NAME, noop_func, NULL)
/* Used for testing floating point operations */
typedef float(float_func)(float input);
typedef double(double_func)(double input);
void selftest_test_float(float_func *func, const char *name, float input);
#define DEF_FLOAT_FUNC(NAME) float selftest_##NAME(float input)
/* Platform-specific tests */
void selftest_check_generic(void);
void selftest_check_x86(void);
void selftest_check_riscv(void);
void selftest_check_aarch64(void);
void selftest_check_arm(void);
#endif /* CHECKASM_TESTS_H */

View File

@@ -1,220 +0,0 @@
#include <inttypes.h>
#include <stdio.h>
#include "tests.h"
#if ARCH_X86
/* Re-use helpers from main checkasm library */
#include "src/cpu.h"
uint64_t selftest_get_cpu_flags_x86(void)
{
uint64_t flags = SELFTEST_CPU_FLAG_X86;
CpuidRegisters r;
checkasm_cpu_cpuid(&r, 0, 0);
const uint32_t max_leaf = r.eax;
if (max_leaf < 1)
return flags;
checkasm_cpu_cpuid(&r, 1, 0);
if (r.edx & 0x00800000) /* MMX */
flags |= SELFTEST_CPU_FLAG_MMX;
if (r.edx & 0x02000000) /* SSE2 */
flags |= SELFTEST_CPU_FLAG_SSE2;
if (~r.ecx & 0x18000000) /* OSXSAVE/AVX */
return flags;
const uint64_t xcr0 = checkasm_cpu_xgetbv(0);
if (max_leaf < 7 || ~xcr0 & 0x6) /* XMM/YMM */
return flags;
checkasm_cpu_cpuid(&r, 7, 0);
if (r.ebx & 0x00000020) /* AVX2 */
flags |= SELFTEST_CPU_FLAG_AVX2;
if (~xcr0 & 0xe0) /* ZMM/OPMASK */
return flags;
if (r.ebx & 0x00000020) /* AVX512F */
flags |= SELFTEST_CPU_FLAG_AVX512;
return flags;
}
DEF_COPY_FUNC(copy_x86);
DEF_COPY_FUNC(copy_mmx);
DEF_COPY_FUNC(copy_sse2);
DEF_COPY_FUNC(copy_avx2);
DEF_COPY_FUNC(copy_avx512);
DEF_NOOP_FUNC(clobber_r0);
DEF_NOOP_FUNC(clobber_r1);
DEF_NOOP_FUNC(clobber_r2);
DEF_NOOP_FUNC(clobber_r3);
DEF_NOOP_FUNC(clobber_r4);
DEF_NOOP_FUNC(clobber_r5);
DEF_NOOP_FUNC(clobber_r6);
#if ARCH_X86_64
DEF_NOOP_FUNC(clobber_r7);
DEF_NOOP_FUNC(clobber_r8);
DEF_NOOP_FUNC(clobber_r9);
DEF_NOOP_FUNC(clobber_r10);
DEF_NOOP_FUNC(clobber_r11);
DEF_NOOP_FUNC(clobber_r12);
DEF_NOOP_FUNC(clobber_r13);
DEF_NOOP_FUNC(clobber_r14);
#endif
DEF_NOOP_FUNC(sigill_x86);
DEF_NOOP_FUNC(corrupt_stack_x86);
DEF_COPY_FUNC(copy_noemms_mmx);
DEF_COPY_FUNC(copy_novzeroupper_avx2);
static copy_func *get_copy_x86(void)
{
const uint64_t flags = checkasm_get_cpu_flags();
#if ARCH_X86
if (flags & SELFTEST_CPU_FLAG_AVX512)
return selftest_copy_avx512;
if (flags & SELFTEST_CPU_FLAG_AVX2)
return selftest_copy_avx2;
if (flags & SELFTEST_CPU_FLAG_SSE2)
return selftest_copy_sse2;
if (flags & SELFTEST_CPU_FLAG_MMX)
return selftest_copy_mmx;
if (flags & SELFTEST_CPU_FLAG_X86)
return selftest_copy_x86;
#endif
return selftest_copy_c;
}
#if ARCH_X86_64
#ifdef _WIN32
#define NUM_SAFE 7
#else
#define NUM_SAFE 9
#endif
#define NUM_REGS 15
#define STACK_ALIGN 16
#else
#define NUM_SAFE 3
#define NUM_REGS 7
#define STACK_ALIGN 4
#endif
static noop_func *get_clobber(int reg)
{
if (!(checkasm_get_cpu_flags() & SELFTEST_CPU_FLAG_X86))
return NULL;
switch (reg) {
case 0: return selftest_clobber_r0;
case 1: return selftest_clobber_r1;
case 2: return selftest_clobber_r2;
case 3: return selftest_clobber_r3;
case 4: return selftest_clobber_r4;
case 5: return selftest_clobber_r5;
case 6: return selftest_clobber_r6;
#if ARCH_X86_64
case 7: return selftest_clobber_r7;
case 8: return selftest_clobber_r8;
case 9: return selftest_clobber_r9;
case 10: return selftest_clobber_r10;
case 11: return selftest_clobber_r11;
case 12: return selftest_clobber_r12;
case 13: return selftest_clobber_r13;
case 14: return selftest_clobber_r14;
#endif
/* can't clobber rsp without completely crashing the program */
default: return NULL;
}
}
DEF_NOOP_GETTER(SELFTEST_CPU_FLAG_X86, sigill_x86)
DEF_NOOP_GETTER(SELFTEST_CPU_FLAG_X86, corrupt_stack_x86)
DEF_COPY_GETTER(SELFTEST_CPU_FLAG_MMX, copy_noemms_mmx)
DEF_COPY_GETTER(SELFTEST_CPU_FLAG_AVX2, copy_novzeroupper_avx2)
uintptr_t selftest_get_stack_pointer_x86(int unused);
static void check_stack_alignment(void)
{
checkasm_declare(uintptr_t, int);
if (checkasm_check_func(selftest_get_stack_pointer_x86, "stack_alignment")) {
uintptr_t sp = checkasm_call_new(0);
/* Subtract return address */
sp -= sizeof(sp);
if (sp & (STACK_ALIGN - 1)) {
if (checkasm_fail()) {
fprintf(stderr, "stack pointer not %d-byte aligned: 0x%" PRIxPTR "\n",
STACK_ALIGN, sp);
}
}
}
}
static void check_clobber(int from, int to)
{
checkasm_declare(void, int);
for (int reg = from; reg < to; reg++) {
noop_func *clobber = get_clobber(reg);
if (!clobber)
break;
if (checkasm_check_func(clobber, "clobber_r%d", reg)) {
checkasm_call_new(0);
}
}
checkasm_report("clobber");
}
static void test_copy_emms(copy_func fun, const char *name)
{
CHECKASM_ALIGN(uint8_t c_dst[256]);
CHECKASM_ALIGN(uint8_t a_dst[256]);
CHECKASM_ALIGN(uint8_t src[256]);
INITIALIZE_BUF(src);
checkasm_declare_emms(SELFTEST_CPU_FLAG_MMX, void, uint8_t *dest,
const uint8_t *src, size_t n);
for (size_t w = 8; w <= 256; w *= 2) {
if (checkasm_check_func(fun, "%s_%zu", name, w)) {
CLEAR_BUF(c_dst);
CLEAR_BUF(a_dst);
checkasm_call_ref(c_dst, src, w);
checkasm_call_new(a_dst, src, w);
checkasm_check(uint8_t, c_dst, 0, a_dst, 0, 256, 1, "dst data");
checkasm_bench_new(a_dst, src, w);
}
}
checkasm_report("%s", name);
}
void selftest_check_x86(void)
{
selftest_test_copy(get_copy_x86(), "copy", 1);
test_copy_emms(get_copy_noemms_mmx(), "copy_noemms");
check_stack_alignment();
check_clobber(0, NUM_SAFE);
if (!checkasm_should_fail(SELFTEST_CPU_FLAG_X86))
return;
selftest_test_noop(get_sigill_x86(), "sigill");
selftest_test_noop(get_corrupt_stack_x86(), "corrupt_stack");
selftest_test_copy(get_copy_noemms_mmx(), "noemms", 8);
check_clobber(NUM_SAFE, NUM_REGS);
if (checkasm_get_check_vzeroupper())
selftest_test_copy(get_copy_novzeroupper_avx2(), "novzeroupper", 32);
}
#endif

View File

@@ -1,113 +0,0 @@
%undef private_prefix
%define private_prefix selftest
%include "src/checkasm_config.asm"
%include "src/x86/x86inc.asm"
SECTION .text
%macro copy_mm 0-1 ; suffix
cglobal copy%1, 3, 3, 1, dst, src, size
cmp sizeq, mmsize
jl copy_x86
add dstq, sizeq
add srcq, sizeq
neg sizeq
.loop:
mova m0, [srcq + sizeq]
mova [dstq + sizeq], m0
add sizeq, mmsize
jl .loop
; emit emms after all MMX functions unless suffix is _noemms
%if mmsize == 8
%ifnidn %1, _noemms
emms
%endif
%endif
; skip vzeroupper if suffix is _novzeroupper
%ifidn %1, _novzeroupper
ret
%else
RET
%endif
%endmacro
; Generic x86 functions
cglobal copy_x86, 3, 3, 0, dst, src, size
%if ARCH_X86_32 || WIN64
push rdi
push rsi
mov rdi, dstq
mov rsi, srcq
%endif
mov rcx, sizeq
rep movsb
%if ARCH_X86_32 || WIN64
pop rsi
pop rdi
%endif
RET
cglobal sigill_x86
ud2
%macro clobber 2 ; register, suffix
cglobal clobber%2
xor %1, %1
RET
%endmacro
clobber r0, _r0
clobber r1, _r1
clobber r2, _r2
clobber r3, _r3
clobber r4, _r4
clobber r5, _r5
clobber r6, _r6
%if ARCH_X86_64
clobber r7, _r7
clobber r8, _r8
clobber r9, _r9
clobber r10, _r10
clobber r11, _r11
clobber r12, _r12
clobber r13, _r13
clobber r14, _r14
%endif
cglobal corrupt_stack_x86
xor rax, rax
%if WIN64
mov [rsp+32+8], rax ; account for shadow space
%else
mov [rsp+8], rax
%endif
RET
cglobal get_stack_pointer_x86
mov rax, rsp
RET
; MMX functions
INIT_MMX mmx
copy_mm
copy_mm _noemms
cglobal noemms, 3, 3, 0, dst, src, size
RET
; SSE2 functions
INIT_XMM sse2
copy_mm
; AVX2 functions
INIT_YMM avx2
copy_mm
copy_mm _novzeroupper
; AVX512 functions
INIT_YMM avx512
copy_mm