Skip to content

Commit 0edda6b

Browse files
committed
Revert nmhash.h and use KATs for big-endian validation
1 parent 3c3ecd2 commit 0edda6b

2 files changed

Lines changed: 139 additions & 39 deletions

File tree

test/hash_functions/nmhash.h

Lines changed: 28 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -89,29 +89,6 @@ extern "C" {
8989
# endif
9090
#endif
9191

92-
/*
93-
* Endian-correct 16-bit multiply for scalar code paths.
94-
* On LE: u16[0] is the low half, u16[1] is the high half.
95-
* On BE: u16[0] is the HIGH half, u16[1] is the LOW half.
96-
* We need low_value *= low_constant, high_value *= high_constant,
97-
* so on BE we swap which constant half goes to which index.
98-
*/
99-
#if NMHASH_LITTLE_ENDIAN
100-
# define NMH_MULT16_SCALAR(u16_0, u16_1, m) do { \
101-
(u16_0) *= (uint16_t)(m); \
102-
(u16_1) *= (uint16_t)((m) >> 16); \
103-
} while(0)
104-
# define NMH_PACK_U16_HI(u16_arr, val) ((u16_arr)[1] = (val))
105-
# define NMH_PACK_U16_LO(u16_arr, val) ((u16_arr)[0] = (val))
106-
#else
107-
# define NMH_MULT16_SCALAR(u16_0, u16_1, m) do { \
108-
(u16_0) *= (uint16_t)((m) >> 16); \
109-
(u16_1) *= (uint16_t)(m); \
110-
} while(0)
111-
# define NMH_PACK_U16_HI(u16_arr, val) ((u16_arr)[0] = (val))
112-
# define NMH_PACK_U16_LO(u16_arr, val) ((u16_arr)[1] = (val))
113-
#endif
114-
11592
/* vector macros */
11693
#define NMH_SCALAR 0
11794
#define NMH_SSE2 1
@@ -229,12 +206,15 @@ NMHASH32_0to8(uint32_t const x, uint32_t const seed2)
229206
union { uint32_t u32; uint16_t u16[2]; } vx;
230207
vx.u32 = x;
231208
vx.u32 ^= (vx.u32 >> 12) ^ (vx.u32 >> 6);
232-
NMH_MULT16_SCALAR(vx.u16[0], vx.u16[1], m1);
209+
vx.u16[0] *= (uint16_t)m1;
210+
vx.u16[1] *= (uint16_t)(m1 >> 16);
233211
vx.u32 ^= (vx.u32 << 11) ^ ( vx.u32 >> 19);
234-
NMH_MULT16_SCALAR(vx.u16[0], vx.u16[1], m2);
212+
vx.u16[0] *= (uint16_t)m2;
213+
vx.u16[1] *= (uint16_t)(m2 >> 16);
235214
vx.u32 ^= seed2;
236215
vx.u32 ^= (vx.u32 >> 15) ^ ( vx.u32 >> 9);
237-
NMH_MULT16_SCALAR(vx.u16[0], vx.u16[1], m3);
216+
vx.u16[0] *= (uint16_t)m3;
217+
vx.u16[1] *= (uint16_t)(m3 >> 16);
238218
vx.u32 ^= (vx.u32 << 16) ^ ( vx.u32 >> 11);
239219
return vx.u32;
240220
}
@@ -310,18 +290,21 @@ NMHASH32_9to255(const uint8_t* const NMH_RESTRICT p, size_t const len, uint32_t
310290
for (j = 0; j < 4; ++j) x[j].u32 += y[j].u32;
311291

312292
for (j = 0; j < 4; ++j) {
313-
NMH_MULT16_SCALAR(x[j].u16[0], x[j].u16[1], __NMH_M1);
293+
x[j].u16[0] *= (uint16_t)(__NMH_M1 & 0xFFFF);
294+
x[j].u16[1] *= (uint16_t)(__NMH_M1 >> 16);
314295
}
315296
for (j = 0; j < 4; ++j) x[j].u32 ^= (x[j].u32 << 5) ^ (x[j].u32 >> 13);
316297
for (j = 0; j < 4; ++j) {
317-
NMH_MULT16_SCALAR(x[j].u16[0], x[j].u16[1], __NMH_M2);
298+
x[j].u16[0] *= (uint16_t)(__NMH_M2 & 0xFFFF);
299+
x[j].u16[1] *= (uint16_t)(__NMH_M2 >> 16);
318300
}
319301

320302
for (j = 0; j < 4; ++j) x[j].u32 ^= y[j].u32;
321303

322304
for (j = 0; j < 4; ++j) x[j].u32 ^= (x[j].u32 << 11) ^ (x[j].u32 >> 9);
323305
for (j = 0; j < 4; ++j) {
324-
NMH_MULT16_SCALAR(x[j].u16[0], x[j].u16[1], __NMH_M3);
306+
x[j].u16[0] *= (uint16_t)(__NMH_M3 & 0xFFFF);
307+
x[j].u16[1] *= (uint16_t)(__NMH_M3 >> 16);
325308
}
326309
for (j = 0; j < 4; ++j) x[j].u32 ^= (x[j].u32 >> 10) ^ (x[j].u32 >> 20);
327310
}
@@ -343,18 +326,21 @@ NMHASH32_9to255(const uint8_t* const NMH_RESTRICT p, size_t const len, uint32_t
343326
for (j = 0; j < 4; ++j) y[j].u32 ^= (y[j].u32 << 17) ^ (y[j].u32 >> 6);
344327

345328
for (j = 0; j < 4; ++j) {
346-
NMH_MULT16_SCALAR(x[j].u16[0], x[j].u16[1], __NMH_M1);
329+
x[j].u16[0] *= (uint16_t)(__NMH_M1 & 0xFFFF);
330+
x[j].u16[1] *= (uint16_t)(__NMH_M1 >> 16);
347331
}
348332
for (j = 0; j < 4; ++j) x[j].u32 ^= (x[j].u32 << 5) ^ (x[j].u32 >> 13);
349333
for (j = 0; j < 4; ++j) {
350-
NMH_MULT16_SCALAR(x[j].u16[0], x[j].u16[1], __NMH_M2);
334+
x[j].u16[0] *= (uint16_t)(__NMH_M2 & 0xFFFF);
335+
x[j].u16[1] *= (uint16_t)(__NMH_M2 >> 16);
351336
}
352337

353338
for (j = 0; j < 4; ++j) x[j].u32 ^= y[j].u32;
354339

355340
for (j = 0; j < 4; ++j) x[j].u32 ^= (x[j].u32 << 11) ^ (x[j].u32 >> 9);
356341
for (j = 0; j < 4; ++j) {
357-
NMH_MULT16_SCALAR(x[j].u16[0], x[j].u16[1], __NMH_M3);
342+
x[j].u16[0] *= (uint16_t)(__NMH_M3 & 0xFFFF);
343+
x[j].u16[1] *= (uint16_t)(__NMH_M3 >> 16);
358344
}
359345
for (j = 0; j < 4; ++j) x[j].u32 ^= (x[j].u32 >> 10) ^ (x[j].u32 >> 20);
360346

@@ -366,7 +352,8 @@ NMHASH32_9to255(const uint8_t* const NMH_RESTRICT p, size_t const len, uint32_t
366352
for (j = 1; j < 4; ++j) x[0].u32 += x[j].u32;
367353

368354
x[0].u32 ^= sl + (sl >> 5);
369-
NMH_MULT16_SCALAR(x[0].u16[0], x[0].u16[1], __NMH_M3);
355+
x[0].u16[0] *= (uint16_t)(__NMH_M3 & 0xFFFF);
356+
x[0].u16[1] *= (uint16_t)(__NMH_M3 >> 16);
370357
x[0].u32 ^= (x[0].u32 >> 10) ^ (x[0].u32 >> 20);
371358

372359
result = x[0].u32;
@@ -594,9 +581,11 @@ NMHASH32_avalanche32(uint32_t const x)
594581
union { uint32_t u32; uint16_t u16[2]; } vx;
595582
vx.u32 = x;
596583
vx.u32 ^= (vx.u32 >> 8) ^ (vx.u32 >> 21);
597-
NMH_MULT16_SCALAR(vx.u16[0], vx.u16[1], m1);
584+
vx.u16[0] = (uint16_t)(vx.u16[0] * (uint16_t)m1);
585+
vx.u16[1] = (uint16_t)(vx.u16[1] * (uint16_t)(m1 >> 16));
598586
vx.u32 ^= (vx.u32 << 12) ^ (vx.u32 >> 7);
599-
NMH_MULT16_SCALAR(vx.u16[0], vx.u16[1], m2);
587+
vx.u16[0] = (uint16_t)(vx.u16[0] * (uint16_t)m2);
588+
vx.u16[1] = (uint16_t)(vx.u16[1] * (uint16_t)(m2 >> 16));
600589
return vx.u32 ^ (vx.u32 >> 8) ^ (vx.u32 >> 21);
601590
}
602591

@@ -628,8 +617,8 @@ NMHASH32(const void* const NMH_RESTRICT input, size_t const len, uint32_t seed)
628617
data.u32 = NMH_readLE16(p);
629618
break;
630619
case 3: seed += NMH_PRIME32_2 + (UINT32_C(3) << 24) + (3 << 1);
631-
NMH_PACK_U16_HI(data.u16, p[2]);
632-
NMH_PACK_U16_LO(data.u16, NMH_readLE16(p));
620+
data.u16[1] = p[2];
621+
data.u16[0] = NMH_readLE16(p);
633622
break;
634623
case 4: seed += NMH_PRIME32_3;
635624
data.u32 = NMH_readLE32(p);
@@ -812,8 +801,8 @@ NMHASH32X(const void* const NMH_RESTRICT input, size_t const len, uint32_t seed)
812801
data.u32 = NMH_readLE16(p);
813802
break;
814803
case 3: seed += NMH_PRIME32_2 + (UINT32_C(3) << 24) + (3 << 1);
815-
NMH_PACK_U16_HI(data.u16, p[2]);
816-
NMH_PACK_U16_LO(data.u16, NMH_readLE16(p));
804+
data.u16[1] = p[2];
805+
data.u16[0] = NMH_readLE16(p);
817806
break;
818807
case 4: seed += NMH_PRIME32_1;
819808
data.u32 = NMH_readLE32(p);

test/hash_functions/test_hash_functions.f90

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ subroutine collect_hash_functions(testsuite)
4343
, new_unittest("spooky_hash", test_spooky_hash) &
4444
, new_unittest("hash_determinism", test_hash_determinism) &
4545
, new_unittest("hash_distribution", test_hash_distribution) &
46+
, new_unittest("nmhash32_kat", test_nmhash32_kat) &
47+
, new_unittest("nmhash32x_kat", test_nmhash32x_kat) &
4648
]
4749

4850
end subroutine collect_hash_functions
@@ -67,6 +69,15 @@ subroutine test_nmhash32(error)
6769
integer(int8) :: key_array(size_key_array)
6870
integer(int32) :: c_hash(0:size_key_array)
6971

72+
! The C reference implementation (nmhash.h) does not support
73+
! big-endian. Skip C-comparison on BE; value-correctness is
74+
! verified by the test_nmhash32_kat known-answer test instead.
75+
if (.not. little_endian) then
76+
call skip_test(error, &
77+
"NMHASH32 C-comparison skipped on Big-Endian (see KAT test)")
78+
return
79+
end if
80+
7081
call read_array("key_array.bin", key_array )
7182

7283
! Read hash array generated from key array by the C version of nmhash32
@@ -88,6 +99,15 @@ subroutine test_nmhash32x(error)
8899
integer(int8) :: key_array(size_key_array)
89100
integer(int32) :: c_hash(0:size_key_array)
90101

102+
! The C reference implementation (nmhash.h) does not support
103+
! big-endian. Skip C-comparison on BE; value-correctness is
104+
! verified by the test_nmhash32x_kat known-answer test instead.
105+
if (.not. little_endian) then
106+
call skip_test(error, &
107+
"NMHASH32X C-comparison skipped on Big-Endian (see KAT test)")
108+
return
109+
end if
110+
91111
call read_array("key_array.bin", key_array )
92112

93113
! Read hash array generated from key array by the C version of nmhash32x
@@ -269,6 +289,97 @@ subroutine test_hash_distribution(error)
269289
end subroutine test_hash_distribution
270290

271291

292+
!> Known-Answer Test for NMHASH32.
293+
!> Verifies the Fortran implementation produces the exact canonical
294+
!> LE-normalized hash values across all code paths. Reference values
295+
!> were computed on a little-endian platform using the upstream C code.
296+
!> This test runs on ALL platforms (LE and BE).
297+
subroutine test_nmhash32_kat(error)
298+
!> Error handling
299+
type(error_type), allocatable, intent(out) :: error
300+
301+
! Number of test vectors
302+
integer, parameter :: num_kat = 14
303+
304+
! Input lengths covering every code path:
305+
! 0=zero, 1/2/3/4=small, 7/8=5-8 path, 9/32=9-32 path,
306+
! 33/100/255=33-255 path, 256/300=long path (256+)
307+
integer, parameter :: kat_lengths(num_kat) = [ &
308+
0, 1, 2, 3, 4, 7, 8, &
309+
9, 32, 33, 100, 255, 256, 300 ]
310+
311+
! Reference NMHASH32 values (computed on LE with seed=0xDEADBEEF)
312+
integer(int32), parameter :: kat_expected(num_kat) = [ &
313+
int(z'B0D9C845', int32), int(z'D52AD23F', int32), &
314+
int(z'E909FDFF', int32), int(z'FF1A009C', int32), &
315+
int(z'097D4183', int32), int(z'55CC8BBF', int32), &
316+
int(z'660D67B4', int32), int(z'CB939B94', int32), &
317+
int(z'4CBE45F8', int32), int(z'2FD88BD0', int32), &
318+
int(z'83AC6B02', int32), int(z'CC0E4E26', int32), &
319+
int(z'567D6B58', int32), int(z'865F0BC9', int32) ]
320+
321+
! Deterministic key: key(i) = IAND(i, 255)
322+
integer(int8) :: key(300)
323+
integer :: i
324+
integer(int32) :: got
325+
326+
do i = 1, 300
327+
key(i) = int(iand(i, 255), int8)
328+
end do
329+
330+
do i = 1, num_kat
331+
got = nmhash32(key(1:kat_lengths(i)), nm_seed)
332+
call check(error, got == kat_expected(i), &
333+
"NMHASH32 KAT failed")
334+
if (allocated(error)) return
335+
end do
336+
337+
end subroutine test_nmhash32_kat
338+
339+
!> Known-Answer Test for NMHASH32X.
340+
!> Same approach as test_nmhash32_kat but for the NMHASH32X variant.
341+
!> This test runs on ALL platforms (LE and BE).
342+
subroutine test_nmhash32x_kat(error)
343+
!> Error handling
344+
type(error_type), allocatable, intent(out) :: error
345+
346+
! Number of test vectors
347+
integer, parameter :: num_kat = 14
348+
349+
! Input lengths covering every code path
350+
integer, parameter :: kat_lengths(num_kat) = [ &
351+
0, 1, 2, 3, 4, 7, 8, &
352+
9, 32, 33, 100, 255, 256, 300 ]
353+
354+
! Reference NMHASH32X values (computed on LE with seed=0xDEADBEEF)
355+
integer(int32), parameter :: kat_expected(num_kat) = [ &
356+
int(z'76844735', int32), int(z'B7AE2C90', int32), &
357+
int(z'EE2224FD', int32), int(z'BBE39609', int32), &
358+
int(z'08467EE3', int32), int(z'10E572DA', int32), &
359+
int(z'2570CFA8', int32), int(z'1A06128A', int32), &
360+
int(z'EABBF1B8', int32), int(z'9B1B3428', int32), &
361+
int(z'F6F0233D', int32), int(z'7EB7CAFC', int32), &
362+
int(z'B34D6C45', int32), int(z'E89BEE9E', int32) ]
363+
364+
! Deterministic key: key(i) = IAND(i, 255)
365+
integer(int8) :: key(300)
366+
integer :: i
367+
integer(int32) :: got
368+
369+
do i = 1, 300
370+
key(i) = int(iand(i, 255), int8)
371+
end do
372+
373+
do i = 1, num_kat
374+
got = nmhash32x(key(1:kat_lengths(i)), nm_seed)
375+
call check(error, got == kat_expected(i), &
376+
"NMHASH32X KAT failed")
377+
if (allocated(error)) return
378+
end do
379+
380+
end subroutine test_nmhash32x_kat
381+
382+
272383
subroutine generate_key_array()
273384

274385
integer :: i, lun

0 commit comments

Comments
 (0)