From cbf7db51dde419e070bb802146fd9a6bb3b865e1 Mon Sep 17 00:00:00 2001 From: Christos Longros <98426896+chrislongros@users.noreply.github.com> Date: Tue, 7 Jul 2026 19:44:30 +0200 Subject: [PATCH] unit/fletcher: test fletcher-2 and fletcher-4 checksums Cover known answers, incremental vs single-call agreement, varsize, byteswap, and cross-implementation equality. Reviewed-by: Brian Behlendorf Signed-off-by: Christos Longros Closes #18747 --- tests/unit/.gitignore | 1 + tests/unit/Makefile.am | 22 +++- tests/unit/test_fletcher.c | 253 +++++++++++++++++++++++++++++++++++++ 3 files changed, 275 insertions(+), 1 deletion(-) create mode 100644 tests/unit/test_fletcher.c diff --git a/tests/unit/.gitignore b/tests/unit/.gitignore index 8a6b4cbde6b8..ed21ec5ae675 100644 --- a/tests/unit/.gitignore +++ b/tests/unit/.gitignore @@ -1,6 +1,7 @@ /test_*.info /test_*_coverage +/test_fletcher /test_zap /test_namecheck /test_btree diff --git a/tests/unit/Makefile.am b/tests/unit/Makefile.am index 234af7bc7c07..0cbfa4d15b8e 100644 --- a/tests/unit/Makefile.am +++ b/tests/unit/Makefile.am @@ -17,7 +17,8 @@ libunit_la_SOURCES = \ UNIT_TESTS = \ %D%/test_zap \ %D%/test_namecheck \ - %D%/test_btree + %D%/test_btree \ + %D%/test_fletcher noinst_PROGRAMS = $(UNIT_TESTS) @@ -67,6 +68,25 @@ nodist_%C%_test_btree_SOURCES = \ libunit.la +%C%_test_fletcher_CFLAGS = $(AM_CFLAGS) + +nodist_%C%_test_fletcher_SOURCES = \ + module/zcommon/zfs_fletcher.c \ + module/zcommon/zfs_fletcher_aarch64_neon.c \ + module/zcommon/zfs_fletcher_avx512.c \ + module/zcommon/zfs_fletcher_intel.c \ + module/zcommon/zfs_fletcher_sse.c \ + module/zcommon/zfs_fletcher_superscalar.c \ + module/zcommon/zfs_fletcher_superscalar4.c + +%C%_test_fletcher_SOURCES = \ + %D%/test_fletcher.c + +%C%_test_fletcher_LDADD = \ + libspl.la \ + libunit.la + + # test run and coverage targets below PHONY += unit unit-coverage unit-coverage-html diff --git a/tests/unit/test_fletcher.c b/tests/unit/test_fletcher.c new file mode 100644 index 000000000000..ad82b9b5acd7 --- /dev/null +++ b/tests/unit/test_fletcher.c @@ -0,0 +1,253 @@ +// SPDX-License-Identifier: CDDL-1.0 +/* + * This file and its contents are supplied under the terms of the + * Common Development and Distribution License ("CDDL"), version 1.0. + * You may only use this file in accordance with the terms of version + * 1.0 of the CDDL. + * + * A full copy of the text of the CDDL should have accompanied this + * source. A copy of the CDDL is also available via the Internet at + * http://www.illumos.org/license/CDDL. + */ + +/* + * Copyright (c) 2026, Christos Longros. + */ + +#include + +#include +#include +#include "zfs_fletcher.h" + +#include "unit.h" + +/* ========== */ + +/* + * Fletcher checksums require the buffer size to be 4-byte aligned, and the + * SIMD implementations process the data in wide strides. We use a buffer that + * is a multiple of every implementation's stride so a single call never has a + * scalar remainder, which keeps the cross-implementation comparison exact. + */ +#define DATA_SIZE 8192 + +static uint8_t databuf[DATA_SIZE] __attribute__((aligned(64))); + +/* Deterministic; distinct bytes per word so native and byteswap differ. */ +static void +fill_data(void) +{ + for (size_t i = 0; i < DATA_SIZE; i++) + databuf[i] = (uint8_t)i; +} + +/* ========== */ + +/* Known answers, verifiable by hand; small buffers take the scalar path. */ +static MunitResult +test_fletcher4_known(const MunitParameter params[], void *data) +{ + (void) params, (void) data; + zio_cksum_t zc; + + /* A single word w yields {w, w, w, w}. */ + const uint32_t one[1] = { 0x01020304u }; + fletcher_4_native(one, sizeof (one), NULL, &zc); + unit_eq(zc.zc_word[0], 0x01020304ULL); + unit_eq(zc.zc_word[1], 0x01020304ULL); + unit_eq(zc.zc_word[2], 0x01020304ULL); + unit_eq(zc.zc_word[3], 0x01020304ULL); + + /* Words {1, 2} yield {1+2, 2*1+2, 3*1+2, 4*1+2}. */ + const uint32_t two[2] = { 1u, 2u }; + fletcher_4_native(two, sizeof (two), NULL, &zc); + unit_eq(zc.zc_word[0], 3); + unit_eq(zc.zc_word[1], 4); + unit_eq(zc.zc_word[2], 5); + unit_eq(zc.zc_word[3], 6); + + /* An empty buffer is the zero checksum. */ + fletcher_4_native(databuf, 0, NULL, &zc); + uint64_t bits = zc.zc_word[0] | zc.zc_word[1] | + zc.zc_word[2] | zc.zc_word[3]; + unit_zero(bits); + + return (MUNIT_OK); +} + +/* Accumulating 4-byte-aligned chunks must match the single-call checksum. */ +static MunitResult +test_fletcher4_incremental(const MunitParameter params[], void *data) +{ + (void) params, (void) data; + fill_data(); + + zio_cksum_t once, inc; + fletcher_4_native(databuf, DATA_SIZE, NULL, &once); + + fletcher_init(&inc); + (void) fletcher_4_incremental_native(databuf, 2048, &inc); + (void) fletcher_4_incremental_native(databuf + 2048, 2048, &inc); + (void) fletcher_4_incremental_native(databuf + 4096, DATA_SIZE - 4096, + &inc); + unit_true(ZIO_CHECKSUM_EQUAL(once, inc)); + + return (MUNIT_OK); +} + +/* varsize equals native when aligned; drops the trailing size % 4 bytes. */ +static MunitResult +test_fletcher4_varsize(const MunitParameter params[], void *data) +{ + (void) params, (void) data; + fill_data(); + + zio_cksum_t native, var; + fletcher_4_native(databuf, 4096, NULL, &native); + fletcher_4_native_varsize(databuf, 4096, &var); + unit_true(ZIO_CHECKSUM_EQUAL(native, var)); + + zio_cksum_t var_unaligned, var_trunc; + fletcher_4_native_varsize(databuf, 4098, &var_unaligned); + fletcher_4_native_varsize(databuf, 4096, &var_trunc); + unit_true(ZIO_CHECKSUM_EQUAL(var_unaligned, var_trunc)); + + return (MUNIT_OK); +} + +/* Native and byteswapped checksums differ on byte-asymmetric data. */ +static MunitResult +test_fletcher4_byteswap(const MunitParameter params[], void *data) +{ + (void) params, (void) data; + fill_data(); + + zio_cksum_t native, swap; + fletcher_4_native(databuf, DATA_SIZE, NULL, &native); + fletcher_4_byteswap(databuf, DATA_SIZE, NULL, &swap); + unit_false(ZIO_CHECKSUM_EQUAL(native, swap)); + + return (MUNIT_OK); +} + +/* Every supported implementation must agree; unsupported ones are skipped. */ +static MunitResult +test_fletcher4_impls(const MunitParameter params[], void *data) +{ + (void) params, (void) data; + fill_data(); + + static const char *const impls[] = { + "scalar", "superscalar", "superscalar4", + "sse2", "ssse3", "avx2", "avx512f", "avx512bw", + "aarch64_neon", NULL, + }; + + /* The generic implementations are always available. */ + unit_eq(fletcher_4_impl_set("scalar"), 0); + unit_eq(fletcher_4_impl_set("superscalar"), 0); + unit_eq(fletcher_4_impl_set("superscalar4"), 0); + + zio_cksum_t ref = { { 0 } }; + boolean_t have_ref = B_FALSE; + + for (int i = 0; impls[i] != NULL; i++) { + if (fletcher_4_impl_set(impls[i]) != 0) + continue; /* not supported on this host */ + + zio_cksum_t zc; + fletcher_4_native(databuf, DATA_SIZE, NULL, &zc); + + if (!have_ref) { + ref = zc; + have_ref = B_TRUE; + } else { + unit_true(ZIO_CHECKSUM_EQUAL(ref, zc)); + } + } + + (void) fletcher_4_impl_set("fastest"); + return (MUNIT_OK); +} + +/* Fletcher-2 known answers, verifiable by hand. It folds 64-bit words. */ +static MunitResult +test_fletcher2_known(const MunitParameter params[], void *data) +{ + (void) params, (void) data; + zio_cksum_t zc; + + /* One pair {x, y} yields {x, y, x, y}. */ + const uint64_t pair[2] = { 1, 2 }; + fletcher_2_native(pair, sizeof (pair), NULL, &zc); + unit_eq(zc.zc_word[0], 1); + unit_eq(zc.zc_word[1], 2); + unit_eq(zc.zc_word[2], 1); + unit_eq(zc.zc_word[3], 2); + + /* Two pairs {w, x, y, z} yield {w+y, x+z, 2w+y, 2x+z}. */ + const uint64_t pairs[4] = { 1, 2, 3, 4 }; + fletcher_2_native(pairs, sizeof (pairs), NULL, &zc); + unit_eq(zc.zc_word[0], 4); + unit_eq(zc.zc_word[1], 6); + unit_eq(zc.zc_word[2], 5); + unit_eq(zc.zc_word[3], 8); + + /* An empty buffer is the zero checksum. */ + fletcher_2_native(databuf, 0, NULL, &zc); + uint64_t bits = zc.zc_word[0] | zc.zc_word[1] | + zc.zc_word[2] | zc.zc_word[3]; + unit_zero(bits); + + return (MUNIT_OK); +} + +/* Fletcher-2: the incremental path must match the single call. */ +static MunitResult +test_fletcher2_incremental(const MunitParameter params[], void *data) +{ + (void) params, (void) data; + fill_data(); + + zio_cksum_t once, inc; + fletcher_2_native(databuf, DATA_SIZE, NULL, &once); + + fletcher_init(&inc); + (void) fletcher_2_incremental_native(databuf, 4096, &inc); + (void) fletcher_2_incremental_native(databuf + 4096, DATA_SIZE - 4096, + &inc); + unit_true(ZIO_CHECKSUM_EQUAL(once, inc)); + + return (MUNIT_OK); +} + +/* ========== */ + +static const MunitTest fletcher_tests[] = { + UNIT_TEST("fletcher4_known", test_fletcher4_known), + UNIT_TEST("fletcher4_incremental", test_fletcher4_incremental), + UNIT_TEST("fletcher4_varsize", test_fletcher4_varsize), + UNIT_TEST("fletcher4_byteswap", test_fletcher4_byteswap), + UNIT_TEST("fletcher4_impls", test_fletcher4_impls), + UNIT_TEST("fletcher2_known", test_fletcher2_known), + UNIT_TEST("fletcher2_incremental", test_fletcher2_incremental), + { 0 }, +}; + +static const MunitSuite fletcher_test_suite = { + "fletcher.", + fletcher_tests, + NULL, + 1, + MUNIT_SUITE_OPTION_NONE, +}; + +int +main(int argc, char **argv) +{ + fletcher_4_init(); + int ret = munit_suite_main(&fletcher_test_suite, NULL, argc, argv); + fletcher_4_fini(); + return (ret); +}