This is an automated email from the git hooks/post-receive script.

Git pushed a commit to branch master
in repository ffmpeg.

commit 5d53a3f9cdfeea9e40ed218aba9d4e36ff206545
Author:     Andreas Rheinhardt <[email protected]>
AuthorDate: Sun Aug 9 16:24:45 2026 +0200
Commit:     Andreas Rheinhardt <[email protected]>
CommitDate: Sun Aug 9 16:24:45 2026 +0200

    Squashed 'tests/checkasm/ext/' changes from e13b0bb3ff..e822e429f3
    
    e822e429f3 utils: Silence an MSVC warning about conversion from double to 
float
    abf8fb0261 utils: Add checkasm_randomize_interval() and float equivalent
    7aea236f8f utils: Simplify checkasm_randomize_range() formulation
    49972c1c72 utils: Fix advertised value range of checkasm_randf()
    11c569cbdc utils: Use appropriate PRNG size for checkasm_init()
    6fd9bafd11 utils: Add way more PRNG helper functions
    d7f10858c8 utils: Use prng() primitive for checkasm_randomize()
    781f16f1ac utils: Parallelize PRNG
    4681280b65 selftest: Add tests for utils.h functions
    02d0b1ee4c Clear CPU state after any checkasm_call()
    48821c5d14 Print cpu mask in JSON output
    5d500889dc tests/selftest: Test CPU flag masking
    dbf5380202 tests: Offset arch-specific CPU flags by 10
    09b4c6a8c9 include/checkasm.h: Add CheckasmCpuInfo.mask
    dd1ca4ed09 utils: Document PRNG re-seeding at the start of each test
    c93961a221 utils: Use substantially more robust PRNG
    e46c473f73 Re-order conditional (mostly cosmetic)
    16738f028d Seed PRNG before running CheckasmTest.init()
    9292f9cf6d Bikeshed summary line a bit more, especially for interrupted runs
    0f03612ec9 Suppress non-failure output after first iteration
    bfdb230dd9 checkasm: Print statusline to track current test/bench progress
    c8996a284b checkasm: Move test iteration variable to global state
    46e136ee0e utils: Add self-repainting statusline buffer
    800053edca Route all log messages through checkasm_fprintf()
    f6791fee59 utils: Redefine checkasm_fprintf() as checkasm_vfprintf()
    629a211feb Add optional CheckasmTest.init() and uninit()
    354f3d3e8d riscv/callcheck: Avoid out-of-range li immediate under LLVM on 
rv32
    6e1b4cd6ca riscv/cpu: Include <asm/unistd.h> for __NR_riscv_hwprobe
    8130043b32 Fix CPU detection for AVX512F
    9760810b49 Support YMM copy tests on processors with only AVX
    dc8c320313 Support XMM copy tests on processors with only SSE
    
    git-subtree-dir: tests/checkasm/ext
    git-subtree-split: e822e429f33e4d02e0815bd497952b3f6deb0a7d
---
 ext-build-test.make         |   1 +
 include/checkasm/checkasm.h |  28 +++++-
 include/checkasm/test.h     |   1 +
 include/checkasm/utils.h    |  78 +++++++++++++++
 src/checkasm.c              | 193 ++++++++++++++++++++++++------------
 src/internal.h              |  21 +++-
 src/riscv/callcheck.S       |   2 +-
 src/riscv/cpu.c             |   1 +
 src/utils.c                 | 234 +++++++++++++++++++++++++++++++++++---------
 src/x86/cpu.c               |   2 +-
 tests/meson.build           |   1 +
 tests/selftest.c            |  39 +++++++-
 tests/tests.h               |  30 +++---
 tests/utils.c               | 187 +++++++++++++++++++++++++++++++++++
 tests/x86/tests.c           |  33 +++----
 tests/x86/tests_asm.asm     |  10 +-
 16 files changed, 707 insertions(+), 154 deletions(-)

diff --git a/ext-build-test.make b/ext-build-test.make
index 3bea491490..c3a0deed92 100644
--- a/ext-build-test.make
+++ b/ext-build-test.make
@@ -31,6 +31,7 @@ OBJS = \
        src/utils.o \
        tests/selftest.o \
        tests/generic.o \
+       tests/utils.o \
        tests/arm/32/tests.o \
        tests/arm/32/tests_asm.o \
        tests/arm/64/tests.o \
diff --git a/include/checkasm/checkasm.h b/include/checkasm/checkasm.h
index fc40fab4cd..c97785e8f5 100644
--- a/include/checkasm/checkasm.h
+++ b/include/checkasm/checkasm.h
@@ -106,6 +106,16 @@ typedef struct CheckasmCpuInfo {
     const char *name;   /**< Human-readable name (e.g., "SSE2", "AVX2") */
     const char *suffix; /**< Short suffix for function names (e.g., "sse2", 
"avx2") */
     CheckasmCpu flag;   /**< Bitmask flag value for this CPU feature */
+
+    /** @brief Bitmask of prior CPU flags to disable again
+     *
+     * Any CPU flags in this mask will be removed from the active CPU flag set
+     * when testing this and any subsequent CPU configurations. This is
+     * effectively the opposite of `flag`.
+     *
+     * @since v1.3.0
+     */
+    CheckasmCpu mask;
 } CheckasmCpuInfo;
 
 /**
@@ -117,6 +127,20 @@ typedef struct CheckasmCpuInfo {
 typedef struct CheckasmTest {
     const char *name;   /**< Name of the test (used for filtering and 
reporting) */
     void (*func)(void); /**< Test function to invoke */
+
+    /**
+     * @brief Optional initialization function
+     *
+     * These functions, if set, are invoked before testing any CPU flags
+     * and after testing all CPU flags, respectively.
+     *
+     * @note If CheckasmConfig.repeat > 1, they will be called for every
+     * iteration.
+     *
+     * @warning These are called even when running checkasm_list_functions().
+     */
+    void (*init)(void);
+    void (*uninit)(void);
 } CheckasmTest;
 
 /**
@@ -362,7 +386,9 @@ CHECKASM_API void checkasm_list_tests(const CheckasmConfig 
*config);
  * @note This requires executing all tests to gather information about the
  *       available functions. During this process, checkasm_check_func() always
  *       returns 0 to skip the actual testing. However, any side effects from
- *       test functions will still occur, unless properly guarded.
+ *       test functions will still occur, unless properly guarded. In
+ *       particular, CheckasmTest.init() and CheckasmTest.uninit() will still
+ *       be executed.
  */
 CHECKASM_API void checkasm_list_functions(const CheckasmConfig *config);
 
diff --git a/include/checkasm/test.h b/include/checkasm/test.h
index d04510c15b..f0bba446b2 100644
--- a/include/checkasm/test.h
+++ b/include/checkasm/test.h
@@ -251,6 +251,7 @@ CHECKASM_API void checkasm_report(const char *name, ...) 
CHECKASM_PRINTF(1, 2);
  */
 #define checkasm_call(func, ...)                                               
          \
     (checkasm_set_signal_handler_state(1), (func) (__VA_ARGS__));              
          \
+    checkasm_clear_cpu_state();                                                
          \
     checkasm_set_signal_handler_state(0)
 
 /**
diff --git a/include/checkasm/utils.h b/include/checkasm/utils.h
index f55a9b16a9..518b4e84e2 100644
--- a/include/checkasm/utils.h
+++ b/include/checkasm/utils.h
@@ -51,6 +51,12 @@
  *
  * These functions use the seed specified in CheckasmConfig (or a time-based
  * seed if not specified) to generate deterministic, reproducible random 
values.
+ *
+ * @note The PRNG state is automatically re-seeded at the start of each test
+ * case (i.e. before each call to CheckasmTest.func), so that different CPU
+ * flags see the same sequence of random numbers, at least until control flow
+ * inside the test function diverges.
+ *
  * @{
  */
 
@@ -66,18 +72,70 @@ CHECKASM_API int checkasm_rand(void);
  */
 CHECKASM_API double checkasm_randf(void);
 
+/**
+ * @brief Generate a random 8-bit unsigned integer
+ * @return Random value in range [0, UINT8_MAX]
+ */
+CHECKASM_API uint8_t checkasm_rand_uint8(void);
+
+/**
+ * @brief Generate a random 16-bit unsigned integer
+ * @return Random value in range [0, UINT16_MAX]
+ */
+CHECKASM_API uint16_t checkasm_rand_uint16(void);
+
 /**
  * @brief Generate a random 32-bit unsigned integer
  * @return Random value in range [0, UINT32_MAX]
  */
 CHECKASM_API uint32_t checkasm_rand_uint32(void);
 
+/**
+ * @brief Generate a random 64-bit unsigned integer
+ * @return Random value in range [0, UINT64_MAX]
+ */
+CHECKASM_API uint64_t checkasm_rand_uint64(void);
+
+/**
+ * @brief Generate a random 8-bit signed integer
+ * @return Random value in range [INT8_MIN, INT8_MAX]
+ */
+CHECKASM_API int8_t checkasm_rand_int8(void);
+
+/**
+ * @brief Generate a random 16-bit signed integer
+ * @return Random value in range [INT16_MIN, INT16_MAX]
+ */
+CHECKASM_API int16_t checkasm_rand_int16(void);
+
 /**
  * @brief Generate a random 32-bit signed integer
  * @return Random value in range [INT32_MIN, INT32_MAX]
  */
 CHECKASM_API int32_t checkasm_rand_int32(void);
 
+/**
+ * @brief Generate a random 64-bit signed integer
+ * @return Random value in range [INT64_MIN, INT64_MAX]
+ */
+CHECKASM_API int64_t checkasm_rand_int64(void);
+
+/**
+ * @brief Generate a truly random 32-bit float.
+ * @return Random float value, drawn from the space of all possible float
+ *         representations, including every possible NaN, Infinity etc.
+ * @see checkasm_randf(), checkasm_rand_norm(), checkasm_rand_dist()
+ */
+CHECKASM_API float checkasm_rand_float32(void);
+
+/**
+ * @brief Generate a truly random 64-bit float.
+ * @return Random float value, drawn from the space of all possible float
+ *         representations, including every possible NaN, Infinity etc.
+ * @see checkasm_randf(), checkasm_rand_norm(), checkasm_rand_dist()
+ */
+CHECKASM_API double checkasm_rand_float64(void);
+
 /** @} */ /* rng */
 
 /**
@@ -160,6 +218,26 @@ CHECKASM_API void checkasm_randomize_range(double *buf, 
int width, double range)
  */
 CHECKASM_API void checkasm_randomize_rangef(float *buf, int width, float 
range);
 
+/**
+ * @brief Fill a double buffer with random values chosen uniformly from an 
interval
+ * @param[out] buf Buffer to fill
+ * @param[in] width Number of elements to randomize
+ * @param[in] low Inclusive lower bound on value
+ * @param[in] high Inclusive upper bound on value
+ */
+CHECKASM_API void checkasm_randomize_interval(double *buf, int width, double 
low,
+                                              double high);
+
+/**
+ * @brief Fill a float buffer with random values chosen uniformly from an 
interval
+ * @param[out] buf Buffer to fill
+ * @param[in] width Number of elements to randomize
+ * @param[in] low Inclusive lower bound on value
+ * @param[in] high Inclusive upper bound on value
+ */
+CHECKASM_API void checkasm_randomize_intervalf(float *buf, int width, float 
low,
+                                               float high);
+
 /**
  * @brief Fill a double buffer with normally distributed random values
  * @param[out] buf Buffer to fill
diff --git a/src/checkasm.c b/src/checkasm.c
index e10cfa23eb..a47308deac 100644
--- a/src/checkasm.c
+++ b/src/checkasm.c
@@ -108,6 +108,7 @@ static struct {
     /* Miscellaneous global state (cosmetic) */
     int max_function_name_length;
     int max_report_name_length;
+    unsigned test_iter;
 
     /* Timing code measurements (aggregated over multiple trials) */
     CheckasmMeasurement nop_cycles;
@@ -210,6 +211,21 @@ static void cpu_info_json(void *priv, const char *fmt, ...)
     checkasm_json_str(json, NULL, buf);
 }
 
+static void cpu_mask_json(CheckasmJson *json, const CheckasmConfig cfg,
+                          const CheckasmCpu cpu_mask)
+{
+    if (!cpu_mask)
+        return;
+
+    checkasm_json_push(json, "mask", '[');
+    for (const CheckasmCpuInfo *info = cfg.cpu_flags; info->flag; info++) {
+        if ((cpu_mask & info->flag) != info->flag)
+            continue;
+        checkasm_json_str(json, NULL, info->suffix);
+    }
+    checkasm_json_pop(json, ']');
+}
+
 struct IterState {
     const char  *test;
     const char  *report;
@@ -277,6 +293,7 @@ static void print_bench_header(struct IterState *const iter)
             checkasm_json_push(json, info->suffix, '{');
             checkasm_json_str(json, "name", info->name);
             checkasm_json(json, "available", available ? "true" : "false");
+            cpu_mask_json(json, cfg, info->mask);
             checkasm_json_pop(json, '}');
         }
         checkasm_json_pop(json, '}'); /* close cpuFlags */
@@ -523,6 +540,12 @@ static int wildstrcmp(const char *str, const char *pattern)
 }
 
 static void handle_interrupt(void);
+static void update_statusline(void);
+
+static int test_enabled(const CheckasmTest *test)
+{
+    return !cfg.test_pattern || !wildstrcmp(test->name, cfg.test_pattern);
+}
 
 /* Perform tests and benchmarks for the specified
  * cpu flag if supported by the host */
@@ -530,6 +553,7 @@ static void check_cpu_flag(const CheckasmCpuInfo *cpu)
 {
     const CheckasmCpu prev_cpu_flags = current.cpu_flags;
     if (cpu) {
+        current.cpu_flags &= ~cpu->mask;
         current.cpu_flags |= cpu->flag & cfg.cpu;
     } else {
         /* Also include any CPU flags not related to the CPU flags list */
@@ -550,9 +574,10 @@ static void check_cpu_flag(const CheckasmCpuInfo *cpu)
         cfg.set_cpu_flags(current.cpu_flags);
 
     for (const CheckasmTest *test = cfg.tests; test->func; test++) {
-        if (cfg.test_pattern && wildstrcmp(test->name, cfg.test_pattern))
+        if (!test_enabled(test))
             continue;
         current.test_name = test->name;
+        update_statusline();
 
         if (checkasm_save_context(checkasm_context)) {
             const char *signal = checkasm_get_last_signal_desc();
@@ -592,8 +617,7 @@ static void check_cpu_flag(const CheckasmCpuInfo *cpu)
 static void print_cpu_name(void)
 {
     if (!current.cpu_name_printed) {
-        checkasm_fprintf(stderr, COLOR_YELLOW, "%s:\n",
-                         current.cpu ? current.cpu->name : "C");
+        LOG_COLOR(COLOR_YELLOW, "%s:\n", current.cpu ? current.cpu->name : 
"C");
         current.cpu_name_printed = 1;
     }
 }
@@ -652,15 +676,15 @@ static int set_cpu_affinity(const unsigned affinity)
 #else
     (void) affinity;
     (void) affinity_err;
-    fprintf(stderr, "checkasm: --affinity is not supported on your system\n");
+    LOG("checkasm: --affinity is not supported on your system\n");
     return 1;
 #endif
 
     if (affinity_err) {
-        fprintf(stderr, "checkasm: invalid cpu affinity (%u)\n", affinity);
+        LOG("checkasm: invalid cpu affinity (%u)\n", affinity);
         return 1;
     } else {
-        fprintf(stderr, "checkasm: running on cpu %u\n", affinity);
+        LOG("checkasm: running on cpu %u\n", affinity);
         return 0;
     }
 }
@@ -697,6 +721,25 @@ static void print_functions(const CheckasmFunc *const f)
     }
 }
 
+static void run_all_tests(void)
+{
+    for (const CheckasmTest *test = cfg.tests; test->func; test++) {
+        if (test->init && test_enabled(test)) {
+            checkasm_srand(cfg.seed);
+            test->init();
+        }
+    }
+
+    check_cpu_flag(NULL);
+    for (const CheckasmCpuInfo *info = cfg.cpu_flags; info->flag; info++)
+        check_cpu_flag(info);
+
+    for (const CheckasmTest *test = cfg.tests; test->func; test++) {
+        if (test->uninit && test_enabled(test))
+            test->uninit();
+    }
+}
+
 void checkasm_list_functions(const CheckasmConfig *config)
 {
     memset(&state, 0, sizeof(state));
@@ -704,9 +747,7 @@ void checkasm_list_functions(const CheckasmConfig *config)
     state.skip_tests = 1;
     cfg              = *config;
 
-    check_cpu_flag(NULL);
-    for (const CheckasmCpuInfo *info = cfg.cpu_flags; info->flag; info++)
-        check_cpu_flag(info);
+    run_all_tests();
 
     print_functions(current.tree.root);
     checkasm_func_tree_uninit(&current.tree);
@@ -726,45 +767,79 @@ static void cpu_fprintf(void *priv, const char *fmt, ...)
 
 static COLD void print_info(void)
 {
-    checkasm_fprintf(stderr, COLOR_YELLOW, "checkasm:\n");
+    LOG_COLOR(COLOR_YELLOW, "checkasm:\n");
     checkasm_cpu_info(cpu_fprintf, stderr, &cfg);
 
     if (cfg.bench) {
-        fprintf(stderr, " - Timing source: %s\n", checkasm_perf.name);
+        LOG(" - Timing source: %s\n", checkasm_perf.name);
         if (cfg.verbose) {
             const CheckasmVar perf_scale = 
checkasm_measurement_result(state.perf_scale);
             const CheckasmVar nop_cycles = 
checkasm_measurement_result(state.nop_cycles);
             const CheckasmVar mhz = checkasm_var_div(checkasm_var_const(1e3), 
perf_scale);
-            fprintf(stderr,
-                    " - Timing resolution: %.4f +/- %.3f ns/%s (%.0f +/- %.1f "
-                    "MHz) (provisional)\n",
-                    checkasm_mode(perf_scale), checkasm_stddev(perf_scale),
-                    checkasm_perf.unit, checkasm_mode(mhz), 
checkasm_stddev(mhz));
-
-            fprintf(stderr,
-                    " - No-op overhead: %.2f +/- %.3f %ss per call 
(provisional)\n",
-                    checkasm_mode(nop_cycles), checkasm_stddev(nop_cycles),
-                    checkasm_perf.unit);
+            LOG(" - Timing resolution: %.4f +/- %.3f ns/%s (%.0f +/- %.1f "
+                "MHz) (provisional)\n",
+                checkasm_mode(perf_scale), checkasm_stddev(perf_scale),
+                checkasm_perf.unit, checkasm_mode(mhz), checkasm_stddev(mhz));
+
+            LOG(" - No-op overhead: %.2f +/- %.3f %ss per call 
(provisional)\n",
+                checkasm_mode(nop_cycles), checkasm_stddev(nop_cycles),
+                checkasm_perf.unit);
         }
-        fprintf(stderr, " - Bench duration: %d µs per function (%" PRIu64 " 
%ss)\n",
-                cfg.bench_usec, state.target_cycles, checkasm_perf.unit);
+        LOG(" - Bench duration: %d µs per function (%" PRIu64 " %ss)\n", 
cfg.bench_usec,
+            state.target_cycles, checkasm_perf.unit);
     }
-    fprintf(stderr, " - Random seed: %u\n", cfg.seed);
+    LOG(" - Random seed: %u\n", cfg.seed);
 }
 
-static int print_summary(void)
+static void update_statusline(void)
 {
+    if (state.skip_tests)
+        return;
+
+    char status[256];
+    int len = 0;
+
+    len += snprintf(status, sizeof(status), "checkasm: iter=%d/%d cpu=%s 
test=%s",
+                    state.test_iter + 1, cfg.repeat, cpu_suffix(current.cpu),
+                    current.test_name);
+
+    if (current.func && current.func->report_name) {
+        snprintf(status + len, sizeof(status) - len, " func=%s",
+                 current.func->report_name);
+    }
+
+    checkasm_statusline(status);
+}
+
+static int print_summary(int interrupted)
+{
+    checkasm_statusline(NULL);
+
+    LOG("checkasm: ");
+    if (interrupted)
+        LOG_COLOR(COLOR_BLUE, "(interrupted) ");
+
     /* Exclude C/ref versions from count reported to user */
     const int num_checked_asm = current.num_checked - current.num_funcs;
     if (current.num_failed) {
-        fprintf(stderr, "checkasm: %d of %d tests failed\n", 
current.num_failed,
-                num_checked_asm);
+        LOG_COLOR(COLOR_RED, "%d ", current.num_failed);
+        LOG("of %d tests failed", num_checked_asm);
     } else if (num_checked_asm) {
-        fprintf(stderr, "checkasm: all %d tests passed\n", num_checked_asm);
+        if (!interrupted)
+            LOG("all ");
+        LOG("%d tests passed", num_checked_asm);
+    } else if (interrupted) {
+        LOG("no tests performed");
     } else {
-        fprintf(stderr, "checkasm: no tests to perform\n");
+        /* User did not define any tests */
+        LOG_COLOR(COLOR_YELLOW, "no tests to perform");
     }
 
+    if (cfg.repeat > 1)
+        LOG(", iteration %d of %d, seed %u\n", state.test_iter + 1, 
cfg.repeat, cfg.seed);
+    else
+        LOG("\n");
+
     if (current.num_benched && !current.num_failed)
         print_benchmarks();
 
@@ -774,8 +849,7 @@ static int print_summary(void)
 static void handle_interrupt(void)
 {
     if (checkasm_interrupted) {
-        fprintf(stderr, "checkasm: interrupted\n");
-        print_summary();
+        print_summary(1);
         exit(128 + checkasm_interrupted);
     }
 }
@@ -784,7 +858,7 @@ int checkasm_run(const CheckasmConfig *config)
 {
 #if !HAVE_HTML_DATA
     if (cfg.format == CHECKASM_FORMAT_HTML) {
-        fprintf(stderr, "checkasm: built without HTML support\n");
+        LOG("checkasm: built without HTML support\n");
         return 1;
     }
 #endif
@@ -837,17 +911,10 @@ int checkasm_run(const CheckasmConfig *config)
 
     print_info();
 
-    for (unsigned i = 0; i < cfg.repeat; i++) {
-        if (i > 0) {
-            checkasm_fprintf(stderr, COLOR_YELLOW, "\nTest #%d:\n", i + 1);
-            fprintf(stderr, " - Random seed: %u\n", cfg.seed);
-        }
-
-        check_cpu_flag(NULL);
-        for (const CheckasmCpuInfo *info = cfg.cpu_flags; info->flag; info++)
-            check_cpu_flag(info);
+    for (state.test_iter = 0; state.test_iter < cfg.repeat; state.test_iter++) 
{
+        run_all_tests();
 
-        int res = print_summary();
+        int res = print_summary(0);
         checkasm_func_tree_uninit(&current.tree);
         if (res)
             return res;
@@ -949,6 +1016,7 @@ CheckasmKey checkasm_check_key(const CheckasmKey version, 
const char *const name
     current.func_ver = v;
     current.num_checked++;
     checkasm_srand(cfg.seed);
+    update_statusline();
 
     if (cfg.bench) {
 #if ARCH_X86
@@ -982,8 +1050,8 @@ static int fail_internal(const char *const msg, va_list 
arg)
     if (v && v->state == CHECKASM_FUNC_OK) {
         if (!current.should_fail) {
             print_cpu_name();
-            checkasm_fprintf(stderr, COLOR_RED, "FAILURE:");
-            fprintf(stderr, " %s_%s (", current.func->name, ver_suffix(v));
+            LOG_COLOR(COLOR_RED, "FAILURE:");
+            LOG(" %s_%s (", current.func->name, ver_suffix(v));
             vfprintf(stderr, msg, arg);
             fputs(")\n", stderr);
         }
@@ -1054,28 +1122,27 @@ void checkasm_report(const char *const name, ...)
         if (current.should_fail)
             current.num_failed = current.prev_failed + (new_checked - fails);
 
-        /* Omit "OK" for non-verbose non-benchmark C function successes */
-        const int want_print = current.num_failed != current.prev_failed
-                            || current.should_fail || cfg.verbose || cfg.bench
-                            || current.cpu;
+        /* Omit 'OK' after the first run, unless failed or verbose */
+        int want_print = current.num_failed != current.prev_failed || 
cfg.verbose;
+        if (state.test_iter == 0)
+            want_print |= current.should_fail || current.cpu;
 
         if (want_print) {
             print_cpu_name();
             if (name) {
-                pad_length -= fprintf(stderr, " - %s.%s", current.test_name, 
report_name);
+                pad_length -= LOG(" - %s.%s", current.test_name, report_name);
             } else {
-                pad_length -= fprintf(stderr, " - %s", current.test_name);
+                pad_length -= LOG(" - %s", current.test_name);
             }
-            fprintf(stderr, "%*c", imax(pad_length, 0) + 2, '[');
+            LOG("%*c", imax(pad_length, 0) + 2, '[');
 
-            if (current.num_failed == current.prev_failed) {
-                checkasm_fprintf(stderr, COLOR_GREEN,
-                                 current.should_fail ? "EXPECTED" : "OK");
-            } else if (!current.should_fail)
-                checkasm_fprintf(stderr, COLOR_RED, "FAILED");
+            if (current.num_failed == current.prev_failed)
+                LOG_COLOR(COLOR_GREEN, current.should_fail ? "EXPECTED" : 
"OK");
+            else if (!current.should_fail)
+                LOG_COLOR(COLOR_RED, "FAILED");
             else
-                checkasm_fprintf(stderr, COLOR_RED, "%d/%d EXPECTED", fails, 
new_checked);
-            fprintf(stderr, "]\n");
+                LOG_COLOR(COLOR_RED, "%d/%d EXPECTED", fails, new_checked);
+            LOG("]\n");
         }
 
         current.prev_checked = current.num_checked;
@@ -1170,13 +1237,13 @@ int checkasm_main(CheckasmConfig *config, int argc, 
const char *argv[])
 #if HAVE_HTML_DATA
             config->format = CHECKASM_FORMAT_HTML;
 #else
-            fprintf(stderr, "checkasm: built without HTML support\n");
+            LOG("checkasm: built without HTML support\n");
             return 1;
 #endif
         } else if (!strncmp(argv[1], "--duration=", 11)) {
             const char *const s = argv[1] + 11;
             if (!parseu(&config->bench_usec, s, 10)) {
-                fprintf(stderr, "checkasm: invalid duration (%s)\n", s);
+                LOG("checkasm: invalid duration (%s)\n", s);
                 print_usage(argv[0]);
                 return 1;
             }
@@ -1198,14 +1265,14 @@ int checkasm_main(CheckasmConfig *config, int argc, 
const char *argv[])
             const char *const s      = argv[1] + 11;
             config->cpu_affinity_set = 1;
             if (!parseu(&config->cpu_affinity, s, 16)) {
-                fprintf(stderr, "checkasm: invalid cpu affinity (%s)\n", s);
+                LOG("checkasm: invalid cpu affinity (%s)\n", s);
                 print_usage(argv[0]);
                 return 1;
             }
         } else if (!strncmp(argv[1], "--repeat=", 9)) {
             const char *const s = argv[1] + 9;
             if (!parseu(&config->repeat, s, 10)) {
-                fprintf(stderr, "checkasm: invalid number of repetitions 
(%s)\n", s);
+                LOG("checkasm: invalid number of repetitions (%s)\n", s);
                 print_usage(argv[0]);
                 return 1;
             }
@@ -1214,7 +1281,7 @@ int checkasm_main(CheckasmConfig *config, int argc, const 
char *argv[])
         } else {
             config->seed_set = 1;
             if (!parseu(&config->seed, argv[1], 10)) {
-                fprintf(stderr, "checkasm: unknown option (%s)\n", argv[1]);
+                LOG("checkasm: unknown option (%s)\n", argv[1]);
                 print_usage(argv[0]);
                 return 1;
             }
diff --git a/src/internal.h b/src/internal.h
index dd698cbc84..6192ede221 100644
--- a/src/internal.h
+++ b/src/internal.h
@@ -109,8 +109,25 @@ NORETURN void checkasm_fail_abort(const char *msg, ...) 
CHECKASM_PRINTF(1, 2);
 
 /* Colored variant of fprintf for terminals that support it */
 void checkasm_setup_fprintf(void);
-void checkasm_fprintf(FILE *const f, const int color, const char *const fmt, 
...)
-    CHECKASM_PRINTF(3, 4);
+int checkasm_vfprintf(FILE *const f, int color, const char *fmt, va_list arg)
+    CHECKASM_PRINTF(3, 0);
+
+CHECKASM_PRINTF(3, 4)
+static inline int checkasm_fprintf(FILE *const f, int color, const char *fmt, 
...)
+{
+    va_list ap;
+    va_start(ap, fmt);
+    int ret = checkasm_vfprintf(f, color, fmt, ap);
+    va_end(ap);
+    return ret;
+}
+
+#define LOG_COLOR(...) checkasm_fprintf(stderr, __VA_ARGS__)
+#define LOG(...)       LOG_COLOR(COLOR_DEFAULT, __VA_ARGS__)
+
+/* Update the statusline, reprinted automatically as needed. Pass NULL or an
+ * empty string ("") to clear it entirely. Maximum 256 characters */
+void checkasm_statusline(const char *status);
 
 /* Light-weight helper for printing nested JSON objects */
 typedef struct CheckasmJson {
diff --git a/src/riscv/callcheck.S b/src/riscv/callcheck.S
index 2fa6d3a873..2fad998eec 100644
--- a/src/riscv/callcheck.S
+++ b/src/riscv/callcheck.S
@@ -312,7 +312,7 @@ function checked_call_ifv, export=1, ext=zve32x
 .Lclobber_v:
         # Clobber the vector registers
         vsetvli t1, zero, e32, m8, ta, ma
-        li      t1, -0xdeadbeef
+        li      t1, 0x0badf00d
         vmv.v.x v0, t1
         vmv.v.x v8, t1
         vmv.v.x v16, t1
diff --git a/src/riscv/cpu.c b/src/riscv/cpu.c
index 9248babedd..4f3121086c 100644
--- a/src/riscv/cpu.c
+++ b/src/riscv/cpu.c
@@ -44,6 +44,7 @@
     #include <sys/hwprobe.h>
   #elif HAVE_ASM_HWPROBE_H
     #include <asm/hwprobe.h>
+    #include <asm/unistd.h>
     #include <sys/syscall.h>
     #include <unistd.h>
 
diff --git a/src/utils.c b/src/utils.c
index 768d3028ef..03c2e71186 100644
--- a/src/utils.c
+++ b/src/utils.c
@@ -119,41 +119,130 @@ unsigned checkasm_seed(void)
     return (unsigned) gettime_nsec(1);
 }
 
-// xor128 from Marsaglia, George (July 2003). "Xorshift RNGs".
-//             Journal of Statistical Software. 8 (14).
-//             doi:10.18637/jss.v008.i14.
-static uint32_t xs_state[4];
+// (parallel) xoshiro128++ from https://prng.di.unimi.it/
+typedef struct CheckasmRand {
+#define CHECKASM_PRNG_NUM 4
+    uint32_t s0[CHECKASM_PRNG_NUM];
+    uint32_t s1[CHECKASM_PRNG_NUM];
+    uint32_t s2[CHECKASM_PRNG_NUM];
+    uint32_t s3[CHECKASM_PRNG_NUM];
+} CheckasmRand;
 
-void checkasm_srand(unsigned seed)
+static CheckasmRand checkasm_prng;
+
+static ALWAYS_INLINE uint32_t rotl(const uint32_t x, int k)
 {
-    xs_state[0] = seed;
-    xs_state[1] = (seed & 0xffff0000) | (~seed & 0x0000ffff);
-    xs_state[2] = (~seed & 0xffff0000) | (seed & 0x0000ffff);
-    xs_state[3] = ~seed;
+    return (x << k) | (x >> (32 - k));
 }
 
-uint32_t checkasm_rand_uint32(void)
+/* Single round of a parallel xoshiro128++, generates a full block */
+static ALWAYS_INLINE void xoshiro128pp(CheckasmRand *restrict xs, uint32_t 
*restrict buf)
 {
-    const uint32_t x = xs_state[0];
-    const uint32_t t = x ^ (x << 11);
+    for (int i = 0; i < CHECKASM_PRNG_NUM; i++) {
+        buf[i] = rotl(xs->s0[i] + xs->s3[i], 7) + xs->s0[i];
 
-    xs_state[0] = xs_state[1];
-    xs_state[1] = xs_state[2];
-    xs_state[2] = xs_state[3];
-    uint32_t w  = xs_state[3];
+        const uint32_t t = xs->s1[i] << 9;
+        xs->s2[i] ^= xs->s0[i];
+        xs->s3[i] ^= xs->s1[i];
+        xs->s1[i] ^= xs->s2[i];
+        xs->s0[i] ^= xs->s3[i];
+        xs->s2[i] ^= t;
+        xs->s3[i] = rotl(xs->s3[i], 11);
+    }
+}
 
-    return xs_state[3] = (w ^ (w >> 19)) ^ (t ^ (t >> 8));
+static void prng(CheckasmRand *restrict xs, uint8_t *restrict buf, size_t size)
+{
+    uint32_t     tmp[CHECKASM_PRNG_NUM];
+    const size_t block_size = sizeof(tmp);
+    CheckasmRand xs_copy    = *xs;
+
+    while (size >= block_size) {
+        xoshiro128pp(&xs_copy, tmp);
+        memcpy(buf, tmp, block_size);
+        buf += block_size;
+        size -= block_size;
+    }
+
+    if (size) {
+        xoshiro128pp(&xs_copy, tmp);
+        memcpy(buf, tmp, size);
+    }
+
+    *xs = xs_copy;
 }
 
-int32_t checkasm_rand_int32(void)
+/* Efficient wrapper for generating individual random integers, by caching
+ * the result of a single call to the underlying generator() */
+static struct {
+    #define PRNG_CACHE_SIZE 64
+    uint8_t  buf8 [PRNG_CACHE_SIZE];
+    uint16_t buf16[PRNG_CACHE_SIZE >> 1];
+    uint32_t buf32[PRNG_CACHE_SIZE >> 2];
+    uint64_t buf64[PRNG_CACHE_SIZE >> 3];
+    int num8;
+    int num16;
+    int num32;
+    int num64;
+} prng_cache;
+
+static_assert(PRNG_CACHE_SIZE % sizeof(uint32_t[CHECKASM_PRNG_NUM]) == 0,
+              "PRNG_CACHE_SIZE should be a multiple of 
uint32_t[CHECKASM_PRNG_NUM]");
+
+#define DEF_CHECKASM_RAND(BITS, TYPE, NAME)                                    
          \
+    TYPE checkasm_rand_##NAME(void)                                            
          \
+    {                                                                          
          \
+        if (!prng_cache.num##BITS) {                                           
          \
+            prng(&checkasm_prng, (uint8_t *) prng_cache.buf##BITS,             
          \
+                 sizeof(prng_cache.buf##BITS));                                
          \
+            prng_cache.num##BITS = ARRAY_SIZE(prng_cache.buf##BITS);           
          \
+        }                                                                      
          \
+                                                                               
          \
+        union {                                                                
          \
+            TYPE           type;                                               
          \
+            uint##BITS##_t raw;                                                
          \
+        } val;                                                                 
          \
+        val.raw = prng_cache.buf##BITS[--prng_cache.num##BITS];                
          \
+        return val.type;                                                       
          \
+    }
+
+DEF_CHECKASM_RAND(8,  int8_t,   int8)
+DEF_CHECKASM_RAND(8,  uint8_t,  uint8)
+DEF_CHECKASM_RAND(16, int16_t,  int16)
+DEF_CHECKASM_RAND(16, uint16_t, uint16)
+DEF_CHECKASM_RAND(32, int32_t,  int32)
+DEF_CHECKASM_RAND(32, uint32_t, uint32)
+DEF_CHECKASM_RAND(32, float,    float32)
+DEF_CHECKASM_RAND(64, int64_t,  int64)
+DEF_CHECKASM_RAND(64, uint64_t, uint64)
+DEF_CHECKASM_RAND(64, double,   float64)
+
+static inline uint64_t splitmix64(uint64_t *state)
+{
+    uint64_t z = (*state += 0x9e3779b97f4a7c15);
+
+    z = (z ^ (z >> 30)) * 0xbf58476d1ce4e5b9;
+    z = (z ^ (z >> 27)) * 0x94d049bb133111eb;
+    return z ^ (z >> 31);
+}
+
+void checkasm_srand(unsigned seed)
 {
-    union {
-        uint32_t u;
-        int32_t  i;
-    } res;
+    /* Seed using splitmix64() as recommended by xoroshiro128 authors */
+    uint64_t s = seed;
+
+    for (int i = 0; i < CHECKASM_PRNG_NUM; i++) {
+        const uint64_t a = splitmix64(&s);
+        const uint64_t b = splitmix64(&s);
+
+        checkasm_prng.s0[i] = (uint32_t) a;
+        checkasm_prng.s1[i] = (uint32_t) b;
+        checkasm_prng.s2[i] = a >> 32;
+        checkasm_prng.s3[i] = b >> 32;
+    }
 
-    res.u = checkasm_rand_uint32();
-    return res.i;
+    /* discard cached random bytes */
+    prng_cache.num8 = prng_cache.num16 = prng_cache.num32 = prng_cache.num64 = 
0;
 }
 
 int checkasm_rand(void)
@@ -164,7 +253,7 @@ int checkasm_rand(void)
 
 double checkasm_randf(void)
 {
-    return checkasm_rand_uint32() / (double) UINT32_MAX;
+    return checkasm_rand_uint32() / (UINT32_MAX + 1.0);
 }
 
 /* Marsaglia polar method */
@@ -198,35 +287,51 @@ double checkasm_rand_dist(CheckasmDist dist)
     return dist.mean + dist.stddev * checkasm_rand_norm();
 }
 
-void checkasm_randomize(void *bufp, size_t bytes)
+void checkasm_randomize(void *buf, size_t bytes)
 {
-    uint8_t *buf = bufp;
-    while (bytes--)
-        *buf++ = checkasm_rand_uint32() & 0xFF;
+    prng(&checkasm_prng, buf, bytes);
 }
 
 void checkasm_randomize_mask8(uint8_t *buf, int width, uint8_t mask)
 {
-    while (width--)
-        *buf++ = checkasm_rand_uint32() & mask;
+    prng(&checkasm_prng, (uint8_t *) buf, width * sizeof(*buf));
+    for (int i = 0; i < width; i++)
+        buf[i] &= mask;
 }
 
 void checkasm_randomize_mask16(uint16_t *buf, int width, uint16_t mask)
 {
-    while (width--)
-        *buf++ = checkasm_rand_uint32() & mask;
+    prng(&checkasm_prng, (uint8_t *) buf, width * sizeof(*buf));
+    for (int i = 0; i < width; i++)
+        buf[i] &= mask;
 }
 
 void checkasm_randomize_range(double *buf, int width, double range)
 {
+    const double scale = range / (UINT32_MAX + 1.0);
     while (width--)
-        *buf++ = checkasm_randf() * range;
+        *buf++ = scale * checkasm_rand_uint32();
 }
 
 void checkasm_randomize_rangef(float *buf, int width, float range)
 {
+    const float scale = (float) (range / (UINT32_MAX + 1.0));
+    while (width--)
+        *buf++ = scale * checkasm_rand_uint32();
+}
+
+void checkasm_randomize_interval(double *buf, int width, double low, double 
high)
+{
+    const double scale = (high - low) / (double) UINT32_MAX;
     while (width--)
-        *buf++ = (float) (checkasm_randf() * range);
+        *buf++ = scale * checkasm_rand_uint32() + low;
+}
+
+void checkasm_randomize_intervalf(float *buf, int width, float low, float high)
+{
+    const float scale = (high - low) / (float) UINT32_MAX;
+    while (width--)
+        *buf++ = scale * checkasm_rand_uint32() + low;
 }
 
 #define RANDOMIZE_DIST(buf, ftype, width, mean, stddev)                        
          \
@@ -338,21 +443,21 @@ void checkasm_init(void *buf, size_t bytes)
         for (int i = 0; i < width; i++, step--) {                              
          \
             if (!step) {                                                       
          \
                 step = imax(shift_rand(width), 1);                             
          \
-                mode = checkasm_rand() & 7;                                    
          \
+                mode = checkasm_rand_uint8() & 7;                              
          \
                 mask = shift_rand(mask_pixel);                                 
          \
             }                                                                  
          \
                                                                                
          \
-            const PIXEL low  = checkasm_rand_uint32() & mask;                  
          \
+            const PIXEL low  = checkasm_rand_uint##BITS() & mask;              
          \
             const PIXEL high = mask_pixel - low;                               
          \
             switch (mode) {                                                    
          \
             case PAT_ZERO:  buf[i] = 0; break;                                 
          \
             case PAT_ONE:   buf[i] = mask_pixel; break;                        
          \
-            case PAT_RAND:  buf[i] = checkasm_rand_uint32() & mask_pixel; 
break;         \
+            case PAT_RAND:  buf[i] = checkasm_rand_uint##BITS() & mask_pixel; 
break;     \
             case PAT_LOW:   buf[i] = low; break;                               
          \
             case PAT_HIGH:  buf[i] = high; break;                              
          \
             case PAT_ALTLO: buf[i] = (i & 1) ? high : low; break;              
          \
             case PAT_ALTHI: buf[i] = (i & 1) ? low : high; break;              
          \
-            case PAT_MIX:   buf[i] = (checkasm_rand() & 1) ? low : high; 
break;            \
+            case PAT_MIX:   buf[i] = (checkasm_rand_uint8() & 1) ? low : high; 
break;    \
             }                                                                  
          \
         }                                                                      
          \
     }
@@ -361,22 +466,57 @@ DEF_CHECKASM_INIT_MASK(8, uint8_t)
 DEF_CHECKASM_INIT_MASK(16, uint16_t)
 
 static int use_printf_color[2];
+static char statusline[256];
+static int statusline_visible;
 
 /* Print colored text to stderr if the terminal supports it */
-void checkasm_fprintf(FILE *const f, const int color, const char *const fmt, 
...)
+int checkasm_vfprintf(FILE *const f, const int color, const char *const fmt, 
va_list arg)
 {
-    va_list arg;
-    int     use_color = use_printf_color[f == stderr];
+    size_t fmt_len = strlen(fmt);
+    int use_color = use_printf_color[f == stderr];
+    if (!use_color || !fmt_len)
+        return vfprintf(f, fmt, arg);
+
+    if (f == stderr && statusline_visible) {
+        fprintf(f, "\r\033[K"); /* clear line */
+        statusline_visible = 0;
+    }
 
-    if (color >= 0 && use_color)
+    if (color >= 0)
         fprintf(f, "\x1b[0;%dm", color);
 
-    va_start(arg, fmt);
-    vfprintf(f, fmt, arg);
-    va_end(arg);
+    int ret = vfprintf(f, fmt, arg);
 
-    if (color >= 0 && use_color)
+    if (color >= 0)
         fprintf(f, "\x1b[0m");
+
+    if (f == stderr && statusline[0] && fmt[fmt_len - 1] == '\n') {
+        fprintf(f, "%s", statusline);
+        statusline_visible = 1;
+    }
+
+    return ret;
+}
+
+void checkasm_statusline(const char *status)
+{
+    if (!status)
+        status = "";
+
+    if (!use_printf_color[1] || !strcmp(statusline, status))
+        return; /* don't re-paint unchanged status */
+
+    snprintf(statusline, sizeof(statusline), "%s", status);
+
+    if (statusline_visible) {
+        fprintf(stderr, "\r\033[K");
+        statusline_visible = 0;
+    }
+
+    if (statusline[0]) {
+        fprintf(stderr, "%s", statusline);
+        statusline_visible = 1;
+    }
 }
 
 static COLD int should_use_color(FILE *const f)
diff --git a/src/x86/cpu.c b/src/x86/cpu.c
index 62fa4c55a0..f404204072 100644
--- a/src/x86/cpu.c
+++ b/src/x86/cpu.c
@@ -167,7 +167,7 @@ static COLD checkasm_simd_warmup_func get_simd_warmup(void)
         return simd_warmup;
 
     checkasm_cpu_cpuid(&r, 7, 0);
-    if (r.ebx & 0x00000020) /* AVX512F */
+    if (r.ebx & 0x00010000) /* AVX512F */
         simd_warmup = checkasm_warmup_avx512;
 
     return simd_warmup;
diff --git a/tests/meson.build b/tests/meson.build
index 384e54e6ae..bac991f5fc 100644
--- a/tests/meson.build
+++ b/tests/meson.build
@@ -42,6 +42,7 @@ checkasm_asm_sources = []
 checkasm_test_sources = files(
   'selftest.c',
   'generic.c',
+  'utils.c',
   'arm/32/tests.c',
   'arm/64/tests.c',
   'riscv/tests.c',
diff --git a/tests/selftest.c b/tests/selftest.c
index 7ac52d7a1b..744c2a132b 100644
--- a/tests/selftest.c
+++ b/tests/selftest.c
@@ -1,13 +1,18 @@
+#include <stdio.h>
+
 #include "tests.h"
 #include <checkasm/checkasm.h>
 
 static const CheckasmCpuInfo cpus[] = {
     { "Bad C",           "badc",    SELFTEST_CPU_FLAG_BAD_C   },
+    { "A",               "a",       SELFTEST_CPU_FLAG_A       },
+    { "B",               "b",       SELFTEST_CPU_FLAG_B, .mask = 
SELFTEST_CPU_FLAG_A },
+    { "AB",              "ab",      SELFTEST_CPU_FLAG_AB      },
 #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    },
+    { "SSE",             "sse",     SELFTEST_CPU_FLAG_SSE     },
+    { "AVX",             "avx",     SELFTEST_CPU_FLAG_AVX     },
     { "AVX-512",         "avx512",  SELFTEST_CPU_FLAG_AVX512  },
 #endif
 #if ARCH_RISCV
@@ -26,8 +31,23 @@ static const CheckasmCpuInfo cpus[] = {
     {0}
 };
 
+static int  seen_c, seen_a, seen_b, seen_ab, seen_any;
+static void selftest_check_flags(void)
+{
+    seen_any = 1;
+
+    switch (checkasm_get_cpu_flags() & SELFTEST_CPU_FLAG_AB) {
+    case 0:                    seen_c  = 1; break;
+    case SELFTEST_CPU_FLAG_A:  seen_a  = 1; break;
+    case SELFTEST_CPU_FLAG_B:  seen_b  = 1; break;
+    case SELFTEST_CPU_FLAG_AB: seen_ab = 1; break;
+    }
+}
+
 static const CheckasmTest tests[] = {
+    { "flags",      selftest_check_flags   },
     { "generic",    selftest_check_generic },
+    { "utils",      selftest_check_utils },
 #if ARCH_X86
     { "x86",        selftest_check_x86     },
 #elif ARCH_RISCV
@@ -45,7 +65,7 @@ int main(int argc, const char *argv[])
     CheckasmConfig cfg = {
         .cpu_flags = cpus,
         .tests     = tests,
-        .cpu       = SELFTEST_CPU_FLAG_BAD_C,
+        .cpu       = SELFTEST_CPU_FLAG_BAD_C | SELFTEST_CPU_FLAG_AB,
     };
 
 #if ARCH_X86
@@ -58,5 +78,16 @@ int main(int argc, const char *argv[])
     cfg.cpu |= selftest_get_cpu_flags_arm();
 #endif
 
-    return checkasm_main(&cfg, argc, argv);
+    int ret = checkasm_main(&cfg, argc, argv);
+    if (ret)
+        return ret;
+
+    if (seen_any && (!seen_c || !seen_a || !seen_b || !seen_ab)) {
+        fprintf(stderr, "checkasm: missing expected CPU flags;\n"
+                        "  seen_c=%d, seen_a=%d, seen_b=%d, seen_ab=%d\n",
+                seen_c, seen_a, seen_b, seen_ab);
+        return 1;
+    }
+
+    return 0;
 }
diff --git a/tests/tests.h b/tests/tests.h
index f3f9c04635..a7e05dc10f 100644
--- a/tests/tests.h
+++ b/tests/tests.h
@@ -37,23 +37,26 @@
 #include <checkasm/utils.h>
 
 enum {
-    SELFTEST_CPU_FLAG_BAD_C = 1 << 0, // dummy flag for "bad" C implementations
+    SELFTEST_CPU_FLAG_BAD_C  = 1 << 0, // dummy flag for "bad" C 
implementations
+    SELFTEST_CPU_FLAG_A      = 1 << 1, // dummy flag for testing flag masking
+    SELFTEST_CPU_FLAG_B      = 1 << 2,
+    SELFTEST_CPU_FLAG_AB     = SELFTEST_CPU_FLAG_A | SELFTEST_CPU_FLAG_B,
 #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,
+    SELFTEST_CPU_FLAG_X86    = 1 << 10,
+    SELFTEST_CPU_FLAG_MMX    = 1 << 11,
+    SELFTEST_CPU_FLAG_SSE    = 1 << 12,
+    SELFTEST_CPU_FLAG_AVX    = 1 << 13,
+    SELFTEST_CPU_FLAG_AVX512 = 1 << 14,
 #elif ARCH_RISCV
-    SELFTEST_CPU_FLAG_RVI    = 1 << 1,
-    SELFTEST_CPU_FLAG_RVF    = 1 << 2,
-    SELFTEST_CPU_FLAG_RVV    = 1 << 3,
+    SELFTEST_CPU_FLAG_RVI    = 1 << 10,
+    SELFTEST_CPU_FLAG_RVF    = 1 << 11,
+    SELFTEST_CPU_FLAG_RVV    = 1 << 12,
 #elif ARCH_AARCH64
-    SELFTEST_CPU_FLAG_AARCH64 = 1 << 1,
+    SELFTEST_CPU_FLAG_AARCH64 = 1 << 10,
 #elif ARCH_ARM
-    SELFTEST_CPU_FLAG_ARM    = 1 << 1,
-    SELFTEST_CPU_FLAG_VFP    = 1 << 2,
-    SELFTEST_CPU_FLAG_VFPD32 = 1 << 3,
+    SELFTEST_CPU_FLAG_ARM    = 1 << 10,
+    SELFTEST_CPU_FLAG_VFP    = 1 << 11,
+    SELFTEST_CPU_FLAG_VFPD32 = 1 << 12,
 #endif
 };
 
@@ -106,6 +109,7 @@ void selftest_test_float(float_func *func, const char 
*name, float input);
 
 /* Platform-specific tests */
 void selftest_check_generic(void);
+void selftest_check_utils(void);
 void selftest_check_x86(void);
 void selftest_check_riscv(void);
 void selftest_check_aarch64(void);
diff --git a/tests/utils.c b/tests/utils.c
new file mode 100644
index 0000000000..172f520a90
--- /dev/null
+++ b/tests/utils.c
@@ -0,0 +1,187 @@
+/*
+ * Copyright © 2026, 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.
+ */
+
+#include "tests.h"
+
+#include "src/internal.h"
+
+static CHECKASM_ALIGN(union {
+    uint8_t  u8[4096];
+    uint16_t u16[2048];
+    float    f32[1024];
+    double   f64[512];
+}) buf;
+
+/* We need a wrapper function because checkasm_declare() can't handle void
+ * parameter lists. This should inline into the benchmark loop. */
+#define WRAP_VOID(RETVAL, FUNC)                                                
          \
+    static ALWAYS_INLINE RETVAL wrap_##FUNC(int unused)                        
          \
+    {                                                                          
          \
+        return FUNC();                                                         
          \
+    }
+
+WRAP_VOID(int,      checkasm_rand)
+WRAP_VOID(int8_t,   checkasm_rand_int8)
+WRAP_VOID(uint8_t,  checkasm_rand_uint8)
+WRAP_VOID(int16_t,  checkasm_rand_int16)
+WRAP_VOID(uint16_t, checkasm_rand_uint16)
+WRAP_VOID(int32_t,  checkasm_rand_int32)
+WRAP_VOID(uint32_t, checkasm_rand_uint32)
+WRAP_VOID(float,    checkasm_rand_float32)
+WRAP_VOID(int64_t,  checkasm_rand_int64)
+WRAP_VOID(uint64_t, checkasm_rand_uint64)
+WRAP_VOID(double,   checkasm_rand_float64)
+
+static void selftest_test_prng(void)
+{
+#define CHECK_RAND(RETVAL, FUNC)                                               
          \
+    if (checkasm_check_func(checkasm_##FUNC, #FUNC)) {                         
          \
+        checkasm_declare(RETVAL, int);                                         
          \
+        checkasm_bench(wrap_checkasm_##FUNC, 0);                               
          \
+    }
+
+    CHECK_RAND(int,      rand)
+    CHECK_RAND(int8_t,   rand_int8)
+    CHECK_RAND(uint8_t,  rand_uint8)
+    CHECK_RAND(int16_t,  rand_int16)
+    CHECK_RAND(uint16_t, rand_uint16)
+    CHECK_RAND(int32_t,  rand_int32)
+    CHECK_RAND(uint32_t, rand_uint32)
+    CHECK_RAND(float,    rand_float32)
+    CHECK_RAND(int64_t,  rand_int64)
+    CHECK_RAND(uint64_t, rand_uint64)
+    CHECK_RAND(double,   rand_float64)
+
+    checkasm_report("prng");
+}
+
+static void selftest_test_randomize(void)
+{
+    if (checkasm_check_func(checkasm_randomize, "randomize")) {
+        checkasm_declare(void, void *buf, size_t bytes);
+        checkasm_bench_new(buf.u8, sizeof(buf.u8));
+    }
+
+    if (checkasm_check_func(checkasm_randomize_mask8, "randomize_mask8")) {
+        checkasm_declare(void, uint8_t *buf, int width, uint8_t mask);
+        checkasm_bench_new(buf.u8, ARRAY_SIZE(buf.u8), 0x7F);
+    }
+
+    if (checkasm_check_func(checkasm_randomize_mask16, "randomize_mask16")) {
+        checkasm_declare(void, uint16_t *buf, int width, uint16_t mask);
+        checkasm_bench_new(buf.u16, ARRAY_SIZE(buf.u16), 0x7FFF);
+    }
+
+    if (checkasm_check_func(checkasm_randomize_range, "randomize_range")) {
+        checkasm_declare(void, double *buf, int width, double range);
+        checkasm_bench_new(buf.f64, ARRAY_SIZE(buf.f64), 100.0);
+    }
+
+    if (checkasm_check_func(checkasm_randomize_rangef, "randomize_rangef")) {
+        checkasm_declare(void, float *buf, int width, float range);
+        checkasm_bench_new(buf.f32, ARRAY_SIZE(buf.f32), 100.0f);
+    }
+
+    if (checkasm_check_func(checkasm_randomize_interval, 
"randomize_interval")) {
+        checkasm_declare(void, double *buf, int width, double low, double 
high);
+        checkasm_bench_new(buf.f64, ARRAY_SIZE(buf.f64), -100.0, 100.0);
+    }
+
+    if (checkasm_check_func(checkasm_randomize_intervalf, 
"randomize_intervalf")) {
+        checkasm_declare(void, float *buf, int width, float low, float high);
+        checkasm_bench_new(buf.f32, ARRAY_SIZE(buf.f32), -100.0f, 100.0f);
+    }
+
+    if (checkasm_check_func(checkasm_randomize_dist, "randomize_dist")) {
+        checkasm_declare(void, double *buf, int width, CheckasmDist dist);
+        checkasm_bench_new(buf.f64, ARRAY_SIZE(buf.f64), 
checkasm_dist_standard);
+    }
+
+    if (checkasm_check_func(checkasm_randomize_distf, "randomize_distf")) {
+        checkasm_declare(void, float *buf, int width, CheckasmDist dist);
+        checkasm_bench_new(buf.f32, ARRAY_SIZE(buf.f32), 
checkasm_dist_standard);
+    }
+
+    if (checkasm_check_func(checkasm_randomize_norm, "randomize_norm")) {
+        checkasm_declare(void, double *buf, int width);
+        checkasm_bench_new(buf.f64, ARRAY_SIZE(buf.f64));
+    }
+
+    if (checkasm_check_func(checkasm_randomize_normf, "randomize_normf")) {
+        checkasm_declare(void, float *buf, int width);
+        checkasm_bench_new(buf.f32, ARRAY_SIZE(buf.f32));
+    }
+
+    checkasm_report("randomize");
+}
+
+static void selftest_test_clear(void)
+{
+    if (checkasm_check_func(checkasm_clear, "clear")) {
+        checkasm_declare(void, void *buf, size_t bytes);
+        checkasm_bench_new(buf.u8, sizeof(buf.u8));
+    }
+
+    if (checkasm_check_func(checkasm_clear8, "clear8")) {
+        checkasm_declare(void, uint8_t *buf, int width, uint8_t val);
+        checkasm_bench_new(buf.u8, ARRAY_SIZE(buf.u8), 0xAA);
+    }
+
+    if (checkasm_check_func(checkasm_clear16, "clear16")) {
+        checkasm_declare(void, uint16_t *buf, int width, uint16_t val);
+        checkasm_bench_new(buf.u16, ARRAY_SIZE(buf.u16), 0xAAAA);
+    }
+
+    checkasm_report("clear");
+}
+
+static void selftest_test_init(void)
+{
+    if (checkasm_check_func(checkasm_init, "init")) {
+        checkasm_declare(void, void *buf, size_t bytes);
+        checkasm_bench_new(buf.u8, sizeof(buf.u8));
+    }
+
+    if (checkasm_check_func(checkasm_init_mask8, "init_mask8")) {
+        checkasm_declare(void, uint8_t *buf, int width, uint8_t mask);
+        checkasm_bench_new(buf.u8, ARRAY_SIZE(buf.u8), 0x7F);
+    }
+
+    if (checkasm_check_func(checkasm_init_mask16, "init_mask16")) {
+        checkasm_declare(void, uint16_t *buf, int width, uint16_t mask);
+        checkasm_bench_new(buf.u16, ARRAY_SIZE(buf.u16), 0x7FFF);
+    }
+
+    checkasm_report("init");
+}
+
+void selftest_check_utils(void)
+{
+    selftest_test_prng();
+    selftest_test_randomize();
+    selftest_test_clear();
+    selftest_test_init();
+}
diff --git a/tests/x86/tests.c b/tests/x86/tests.c
index 7c7277d9d5..377ec428a0 100644
--- a/tests/x86/tests.c
+++ b/tests/x86/tests.c
@@ -20,31 +20,30 @@ uint64_t selftest_get_cpu_flags_x86(void)
     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.edx & 0x02000000) /* SSE */
+        flags |= SELFTEST_CPU_FLAG_SSE;
     if (~r.ecx & 0x18000000) /* OSXSAVE/AVX */
         return flags;
 
     const uint64_t xcr0 = checkasm_cpu_xgetbv(0);
-    if (max_leaf < 7 || ~xcr0 & 0x6) /* XMM/YMM */
+    if (~xcr0 & 0x6) /* XMM/YMM */
         return flags;
 
-    checkasm_cpu_cpuid(&r, 7, 0);
-    if (r.ebx & 0x00000020) /* AVX2 */
-        flags |= SELFTEST_CPU_FLAG_AVX2;
+    flags |= SELFTEST_CPU_FLAG_AVX;
 
-    if (~xcr0 & 0xe0) /* ZMM/OPMASK */
+    if (max_leaf < 7 || ~xcr0 & 0xe0) /* ZMM/OPMASK */
         return flags;
 
-    if (r.ebx & 0x00000020) /* AVX512F */
+    checkasm_cpu_cpuid(&r, 7, 0);
+    if (r.ebx & 0x00010000) /* 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_sse);
+DEF_COPY_FUNC(copy_avx);
 DEF_COPY_FUNC(copy_avx512);
 
 DEF_NOOP_FUNC(clobber_r0);
@@ -69,7 +68,7 @@ DEF_NOOP_FUNC(sigill_x86);
 DEF_NOOP_FUNC(corrupt_stack_x86);
 
 DEF_COPY_FUNC(copy_noemms_mmx);
-DEF_COPY_FUNC(copy_novzeroupper_avx2);
+DEF_COPY_FUNC(copy_novzeroupper_avx);
 
 static copy_func *get_copy_x86(void)
 {
@@ -77,10 +76,10 @@ static copy_func *get_copy_x86(void)
 #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_AVX)
+        return selftest_copy_avx;
+    if (flags & SELFTEST_CPU_FLAG_SSE)
+        return selftest_copy_sse;
     if (flags & SELFTEST_CPU_FLAG_MMX)
         return selftest_copy_mmx;
     if (flags & SELFTEST_CPU_FLAG_X86)
@@ -134,7 +133,7 @@ static noop_func *get_clobber(int reg)
 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)
+DEF_COPY_GETTER(SELFTEST_CPU_FLAG_AVX, copy_novzeroupper_avx)
 
 uintptr_t selftest_get_stack_pointer_x86(int unused);
 
@@ -214,7 +213,7 @@ void selftest_check_x86(void)
     check_clobber(NUM_SAFE, NUM_REGS);
 
     if (checkasm_get_check_vzeroupper())
-        selftest_test_copy(get_copy_novzeroupper_avx2(), "novzeroupper", 32);
+        selftest_test_copy(get_copy_novzeroupper_avx(), "novzeroupper", 32);
 }
 
 #endif
diff --git a/tests/x86/tests_asm.asm b/tests/x86/tests_asm.asm
index 1af9e12dae..a2eeff47ee 100644
--- a/tests/x86/tests_asm.asm
+++ b/tests/x86/tests_asm.asm
@@ -99,15 +99,15 @@ copy_mm _noemms
 cglobal noemms, 3, 3, 0, dst, src, size
     RET
 
-; SSE2 functions
-INIT_XMM sse2
+; SSE functions
+INIT_XMM sse
 copy_mm
 
-; AVX2 functions
-INIT_YMM avx2
+; AVX functions
+INIT_YMM avx
 copy_mm
 copy_mm _novzeroupper
 
 ; AVX512 functions
-INIT_YMM avx512
+INIT_ZMM avx512
 copy_mm

_______________________________________________
ffmpeg-cvslog mailing list -- [email protected]
To unsubscribe send an email to [email protected]

Reply via email to