xref: /freebsd/sys/contrib/openzfs/tests/unit/test_fletcher.c (revision 22649d4dba730d46244fd2dff4fd174903c8379f)
1 // SPDX-License-Identifier: CDDL-1.0
2 /*
3  * This file and its contents are supplied under the terms of the
4  * Common Development and Distribution License ("CDDL"), version 1.0.
5  * You may only use this file in accordance with the terms of version
6  * 1.0 of the CDDL.
7  *
8  * A full copy of the text of the CDDL should have accompanied this
9  * source.  A copy of the CDDL is also available via the Internet at
10  * https://opensource.org/license/CDDL-1.0.
11  */
12 
13 /*
14  * Copyright (c) 2026, Christos Longros.
15  */
16 
17 #include <string.h>
18 
19 #include <sys/types.h>
20 #include <sys/spa_checksum.h>
21 #include "zfs_fletcher.h"
22 
23 #include "unit.h"
24 
25 /* ========== */
26 
27 /*
28  * Fletcher checksums require the buffer size to be 4-byte aligned, and the
29  * SIMD implementations process the data in wide strides.  We use a buffer that
30  * is a multiple of every implementation's stride so a single call never has a
31  * scalar remainder, which keeps the cross-implementation comparison exact.
32  */
33 #define	DATA_SIZE	8192
34 
35 static uint8_t databuf[DATA_SIZE] __attribute__((aligned(64)));
36 
37 /* Deterministic; distinct bytes per word so native and byteswap differ. */
38 static void
fill_data(void)39 fill_data(void)
40 {
41 	for (size_t i = 0; i < DATA_SIZE; i++)
42 		databuf[i] = (uint8_t)i;
43 }
44 
45 /* ========== */
46 
47 /* Known answers, verifiable by hand; small buffers take the scalar path. */
48 static MunitResult
test_fletcher4_known(const MunitParameter params[],void * data)49 test_fletcher4_known(const MunitParameter params[], void *data)
50 {
51 	(void) params, (void) data;
52 	zio_cksum_t zc;
53 
54 	/* A single word w yields {w, w, w, w}. */
55 	const uint32_t one[1] = { 0x01020304u };
56 	fletcher_4_native(one, sizeof (one), NULL, &zc);
57 	unit_eq(zc.zc_word[0], 0x01020304ULL);
58 	unit_eq(zc.zc_word[1], 0x01020304ULL);
59 	unit_eq(zc.zc_word[2], 0x01020304ULL);
60 	unit_eq(zc.zc_word[3], 0x01020304ULL);
61 
62 	/* Words {1, 2} yield {1+2, 2*1+2, 3*1+2, 4*1+2}. */
63 	const uint32_t two[2] = { 1u, 2u };
64 	fletcher_4_native(two, sizeof (two), NULL, &zc);
65 	unit_eq(zc.zc_word[0], 3);
66 	unit_eq(zc.zc_word[1], 4);
67 	unit_eq(zc.zc_word[2], 5);
68 	unit_eq(zc.zc_word[3], 6);
69 
70 	/* An empty buffer is the zero checksum. */
71 	fletcher_4_native(databuf, 0, NULL, &zc);
72 	uint64_t bits = zc.zc_word[0] | zc.zc_word[1] |
73 	    zc.zc_word[2] | zc.zc_word[3];
74 	unit_zero(bits);
75 
76 	return (MUNIT_OK);
77 }
78 
79 /* Accumulating 4-byte-aligned chunks must match the single-call checksum. */
80 static MunitResult
test_fletcher4_incremental(const MunitParameter params[],void * data)81 test_fletcher4_incremental(const MunitParameter params[], void *data)
82 {
83 	(void) params, (void) data;
84 	fill_data();
85 
86 	zio_cksum_t once, inc;
87 	fletcher_4_native(databuf, DATA_SIZE, NULL, &once);
88 
89 	fletcher_init(&inc);
90 	(void) fletcher_4_incremental_native(databuf, 2048, &inc);
91 	(void) fletcher_4_incremental_native(databuf + 2048, 2048, &inc);
92 	(void) fletcher_4_incremental_native(databuf + 4096, DATA_SIZE - 4096,
93 	    &inc);
94 	unit_true(ZIO_CHECKSUM_EQUAL(once, inc));
95 
96 	return (MUNIT_OK);
97 }
98 
99 /* varsize equals native when aligned; drops the trailing size % 4 bytes. */
100 static MunitResult
test_fletcher4_varsize(const MunitParameter params[],void * data)101 test_fletcher4_varsize(const MunitParameter params[], void *data)
102 {
103 	(void) params, (void) data;
104 	fill_data();
105 
106 	zio_cksum_t native, var;
107 	fletcher_4_native(databuf, 4096, NULL, &native);
108 	fletcher_4_native_varsize(databuf, 4096, &var);
109 	unit_true(ZIO_CHECKSUM_EQUAL(native, var));
110 
111 	zio_cksum_t var_unaligned, var_trunc;
112 	fletcher_4_native_varsize(databuf, 4098, &var_unaligned);
113 	fletcher_4_native_varsize(databuf, 4096, &var_trunc);
114 	unit_true(ZIO_CHECKSUM_EQUAL(var_unaligned, var_trunc));
115 
116 	return (MUNIT_OK);
117 }
118 
119 /* Native and byteswapped checksums differ on byte-asymmetric data. */
120 static MunitResult
test_fletcher4_byteswap(const MunitParameter params[],void * data)121 test_fletcher4_byteswap(const MunitParameter params[], void *data)
122 {
123 	(void) params, (void) data;
124 	fill_data();
125 
126 	zio_cksum_t native, swap;
127 	fletcher_4_native(databuf, DATA_SIZE, NULL, &native);
128 	fletcher_4_byteswap(databuf, DATA_SIZE, NULL, &swap);
129 	unit_false(ZIO_CHECKSUM_EQUAL(native, swap));
130 
131 	return (MUNIT_OK);
132 }
133 
134 /* Every supported implementation must agree; unsupported ones are skipped. */
135 static MunitResult
test_fletcher4_impls(const MunitParameter params[],void * data)136 test_fletcher4_impls(const MunitParameter params[], void *data)
137 {
138 	(void) params, (void) data;
139 	fill_data();
140 
141 	static const char *const impls[] = {
142 		"scalar", "superscalar", "superscalar4",
143 		"sse2", "ssse3", "avx2", "avx512f", "avx512bw",
144 		"aarch64_neon", NULL,
145 	};
146 
147 	/* The generic implementations are always available. */
148 	unit_eq(fletcher_4_impl_set("scalar"), 0);
149 	unit_eq(fletcher_4_impl_set("superscalar"), 0);
150 	unit_eq(fletcher_4_impl_set("superscalar4"), 0);
151 
152 	zio_cksum_t ref = { { 0 } };
153 	boolean_t have_ref = B_FALSE;
154 
155 	for (int i = 0; impls[i] != NULL; i++) {
156 		if (fletcher_4_impl_set(impls[i]) != 0)
157 			continue;	/* not supported on this host */
158 
159 		zio_cksum_t zc;
160 		fletcher_4_native(databuf, DATA_SIZE, NULL, &zc);
161 
162 		if (!have_ref) {
163 			ref = zc;
164 			have_ref = B_TRUE;
165 		} else {
166 			unit_true(ZIO_CHECKSUM_EQUAL(ref, zc));
167 		}
168 	}
169 
170 	(void) fletcher_4_impl_set("fastest");
171 	return (MUNIT_OK);
172 }
173 
174 /* Fletcher-2 known answers, verifiable by hand.  It folds 64-bit words. */
175 static MunitResult
test_fletcher2_known(const MunitParameter params[],void * data)176 test_fletcher2_known(const MunitParameter params[], void *data)
177 {
178 	(void) params, (void) data;
179 	zio_cksum_t zc;
180 
181 	/* One pair {x, y} yields {x, y, x, y}. */
182 	const uint64_t pair[2] = { 1, 2 };
183 	fletcher_2_native(pair, sizeof (pair), NULL, &zc);
184 	unit_eq(zc.zc_word[0], 1);
185 	unit_eq(zc.zc_word[1], 2);
186 	unit_eq(zc.zc_word[2], 1);
187 	unit_eq(zc.zc_word[3], 2);
188 
189 	/* Two pairs {w, x, y, z} yield {w+y, x+z, 2w+y, 2x+z}. */
190 	const uint64_t pairs[4] = { 1, 2, 3, 4 };
191 	fletcher_2_native(pairs, sizeof (pairs), NULL, &zc);
192 	unit_eq(zc.zc_word[0], 4);
193 	unit_eq(zc.zc_word[1], 6);
194 	unit_eq(zc.zc_word[2], 5);
195 	unit_eq(zc.zc_word[3], 8);
196 
197 	/* An empty buffer is the zero checksum. */
198 	fletcher_2_native(databuf, 0, NULL, &zc);
199 	uint64_t bits = zc.zc_word[0] | zc.zc_word[1] |
200 	    zc.zc_word[2] | zc.zc_word[3];
201 	unit_zero(bits);
202 
203 	return (MUNIT_OK);
204 }
205 
206 /* Fletcher-2: the incremental path must match the single call. */
207 static MunitResult
test_fletcher2_incremental(const MunitParameter params[],void * data)208 test_fletcher2_incremental(const MunitParameter params[], void *data)
209 {
210 	(void) params, (void) data;
211 	fill_data();
212 
213 	zio_cksum_t once, inc;
214 	fletcher_2_native(databuf, DATA_SIZE, NULL, &once);
215 
216 	fletcher_init(&inc);
217 	(void) fletcher_2_incremental_native(databuf, 4096, &inc);
218 	(void) fletcher_2_incremental_native(databuf + 4096, DATA_SIZE - 4096,
219 	    &inc);
220 	unit_true(ZIO_CHECKSUM_EQUAL(once, inc));
221 
222 	return (MUNIT_OK);
223 }
224 
225 /* ========== */
226 
227 static const MunitTest fletcher_tests[] = {
228 	UNIT_TEST("fletcher4_known",		test_fletcher4_known),
229 	UNIT_TEST("fletcher4_incremental",	test_fletcher4_incremental),
230 	UNIT_TEST("fletcher4_varsize",		test_fletcher4_varsize),
231 	UNIT_TEST("fletcher4_byteswap",		test_fletcher4_byteswap),
232 	UNIT_TEST("fletcher4_impls",		test_fletcher4_impls),
233 	UNIT_TEST("fletcher2_known",		test_fletcher2_known),
234 	UNIT_TEST("fletcher2_incremental",	test_fletcher2_incremental),
235 	{ 0 },
236 };
237 
238 static const MunitSuite fletcher_test_suite = {
239 	"fletcher.",
240 	fletcher_tests,
241 	NULL,
242 	1,
243 	MUNIT_SUITE_OPTION_NONE,
244 };
245 
246 int
main(int argc,char ** argv)247 main(int argc, char **argv)
248 {
249 	fletcher_4_init();
250 	int ret = munit_suite_main(&fletcher_test_suite, NULL, argc, argv);
251 	fletcher_4_fini();
252 	return (ret);
253 }
254