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