@@ -344,6 +344,277 @@ load_le(::Type{T}, ptr::Ptr{UInt8}, i) where {T <: Union{UInt32, UInt64}} =
344
344
return hash_mix (a ⊻ secret[4 ], b ⊻ secret[2 ] ⊻ i)
345
345
end
346
346
347
+ @inline function load_le_array (:: Type{UInt64} , arr:: AbstractArray{UInt8} , idx)
348
+ # n.b. for whatever reason, writing this as a loop ensures LLVM
349
+ # optimizations (particular SROA) don't make a disaster of this code
350
+ # early on so it can actually emit the optimal result
351
+ result = zero (UInt64)
352
+ for i in 0 : 7
353
+ byte = @inbounds arr[idx + i]
354
+ result |= UInt64 (byte) << (8 * i)
355
+ end
356
+ return result
357
+ end
358
+
359
+ @inline function load_le_array (:: Type{UInt32} , arr:: AbstractArray{UInt8} , idx)
360
+ result = zero (UInt32)
361
+ for i in 0 : 3
362
+ byte = @inbounds arr[idx + i]
363
+ result |= UInt32 (byte) << (8 * i)
364
+ end
365
+ return result
366
+ end
367
+
368
+ @assume_effects :terminates_globally function hash_bytes (
369
+ arr:: AbstractArray{UInt8} ,
370
+ seed:: UInt64 ,
371
+ secret:: NTuple{4, UInt64}
372
+ )
373
+ # Adapted with gratitude from [rapidhash](https://github.com/Nicoshev/rapidhash)
374
+ n = length (arr)
375
+ buflen = UInt64 (n)
376
+ seed = seed ⊻ hash_mix (seed ⊻ secret[3 ], secret[2 ])
377
+ firstidx = firstindex (arr)
378
+
379
+ a = zero (UInt64)
380
+ b = zero (UInt64)
381
+ i = buflen
382
+
383
+ if buflen ≤ 16
384
+ if buflen ≥ 4
385
+ seed ⊻= buflen
386
+ if buflen ≥ 8
387
+ a = load_le_array (UInt64, arr, firstidx)
388
+ b = load_le_array (UInt64, arr, firstidx + n - 8 )
389
+ else
390
+ a = UInt64 (load_le_array (UInt32, arr, firstidx))
391
+ b = UInt64 (load_le_array (UInt32, arr, firstidx + n - 4 ))
392
+ end
393
+ elseif buflen > 0
394
+ a = (UInt64 (@inbounds arr[firstidx]) << 45 ) | UInt64 (@inbounds arr[firstidx + n - 1 ])
395
+ b = UInt64 (@inbounds arr[firstidx + div (n, 2 )])
396
+ end
397
+ else
398
+ pos = 0
399
+ if i > 48
400
+ see1 = seed
401
+ see2 = seed
402
+ while i > 48
403
+ seed = hash_mix (
404
+ load_le_array (UInt64, arr, firstidx + pos) ⊻ secret[1 ],
405
+ load_le_array (UInt64, arr, firstidx + pos + 8 ) ⊻ seed
406
+ )
407
+ see1 = hash_mix (
408
+ load_le_array (UInt64, arr, firstidx + pos + 16 ) ⊻ secret[2 ],
409
+ load_le_array (UInt64, arr, firstidx + pos + 24 ) ⊻ see1
410
+ )
411
+ see2 = hash_mix (
412
+ load_le_array (UInt64, arr, firstidx + pos + 32 ) ⊻ secret[3 ],
413
+ load_le_array (UInt64, arr, firstidx + pos + 40 ) ⊻ see2
414
+ )
415
+ pos += 48
416
+ i -= 48
417
+ end
418
+ seed ⊻= see1
419
+ seed ⊻= see2
420
+ end
421
+ if i > 16
422
+ seed = hash_mix (
423
+ load_le_array (UInt64, arr, firstidx + pos) ⊻ secret[3 ],
424
+ load_le_array (UInt64, arr, firstidx + pos + 8 ) ⊻ seed
425
+ )
426
+ if i > 32
427
+ seed = hash_mix (
428
+ load_le_array (UInt64, arr, firstidx + pos + 16 ) ⊻ secret[3 ],
429
+ load_le_array (UInt64, arr, firstidx + pos + 24 ) ⊻ seed
430
+ )
431
+ end
432
+ end
433
+
434
+ a = load_le_array (UInt64, arr, firstidx + n - 16 ) ⊻ i
435
+ b = load_le_array (UInt64, arr, firstidx + n - 8 )
436
+ end
437
+
438
+ a = a ⊻ secret[2 ]
439
+ b = b ⊻ seed
440
+ b, a = mul_parts (a, b)
441
+ return hash_mix (a ⊻ secret[4 ], b ⊻ secret[2 ] ⊻ i)
442
+ end
443
+
444
+
445
+ # Helper function to concatenate two UInt64 values with a byte shift
446
+ # Returns the result of shifting 'low' right by 'shift_bytes' bytes and
447
+ # filling the high bits with the low bits of 'high'
448
+ @inline function concat_shift (low:: UInt64 , high:: UInt64 , shift_bytes:: UInt8 )
449
+ shift_bits = (shift_bytes * 0x8 ) & 0x3f
450
+ return (low >> shift_bits) | (high << (0x40 - shift_bits))
451
+ end
452
+
453
+ @inline function read_uint64_from_uint8_iter (iter, state)
454
+ value = zero (UInt64)
455
+ @nexprs 8 i -> begin
456
+ next_result = iterate (iter, state)
457
+ next_result === nothing && return value, state, UInt8 (i - 1 )
458
+ byte, state = next_result
459
+ value |= UInt64 (byte) << ((i - 1 ) * 8 )
460
+ end
461
+ return value, state, 0x8
462
+ end
463
+
464
+ @inline function read_uint64_from_uint8_iter (iter)
465
+ next_result = iterate (iter)
466
+ next_result === nothing && return nothing
467
+ byte, state = next_result
468
+ value = UInt64 (byte)
469
+ @nexprs 7 i -> begin
470
+ next_result = iterate (iter, state)
471
+ next_result === nothing && return value, state, UInt8 (i)
472
+ byte, state = next_result
473
+ value |= UInt64 (byte:: UInt8 ) << (i * 8 )
474
+ end
475
+ return value, state, 0x8
476
+ end
477
+
478
+ @assume_effects :terminates_globally function hash_bytes (
479
+ iter,
480
+ seed:: UInt64 ,
481
+ secret:: NTuple{4, UInt64}
482
+ )
483
+ seed = seed ⊻ hash_mix (seed ⊻ secret[3 ], secret[2 ])
484
+
485
+ a = zero (UInt64)
486
+ b = zero (UInt64)
487
+ buflen = zero (UInt64)
488
+
489
+ see1 = seed
490
+ see2 = seed
491
+ l0 = zero (UInt64)
492
+ l1 = zero (UInt64)
493
+ l2 = zero (UInt64)
494
+ l3 = zero (UInt64)
495
+ l4 = zero (UInt64)
496
+ l5 = zero (UInt64)
497
+ b0 = 0x0
498
+ b1 = 0x0
499
+ b2 = 0x0
500
+ b3 = 0x0
501
+ b4 = 0x0
502
+ b5 = 0x0
503
+ t0 = zero (UInt64)
504
+ t1 = zero (UInt64)
505
+
506
+ # Handle first iteration separately
507
+ read = read_uint64_from_uint8_iter (iter)
508
+ if read != = nothing
509
+ l0, state, b0 = read
510
+ # Repeat hashing chunks until a short read
511
+ while true
512
+ l1, state, b1 = read_uint64_from_uint8_iter (iter, state)
513
+ if b1 == 0x8
514
+ l2, state, b2 = read_uint64_from_uint8_iter (iter, state)
515
+ if b2 == 0x8
516
+ l3, state, b3 = read_uint64_from_uint8_iter (iter, state)
517
+ if b3 == 0x8
518
+ l4, state, b4 = read_uint64_from_uint8_iter (iter, state)
519
+ if b4 == 0x8
520
+ l5, state, b5 = read_uint64_from_uint8_iter (iter, state)
521
+ if b5 == 0x8
522
+ # Read start of next chunk
523
+ read = read_uint64_from_uint8_iter (iter, state)
524
+ if read[3 ] == 0x0
525
+ # Read exactly 48 bytes
526
+ t0 = l4
527
+ t1 = l5
528
+ break
529
+ else
530
+ # Read more than 48 bytes - process and continue to next chunk
531
+ seed = hash_mix (l0 ⊻ secret[1 ], l1 ⊻ seed)
532
+ see1 = hash_mix (l2 ⊻ secret[2 ], l3 ⊻ see1)
533
+ see2 = hash_mix (l4 ⊻ secret[3 ], l5 ⊻ see2)
534
+ buflen += 48
535
+ l0, state, b0 = read
536
+ b1 = 0
537
+ b2 = 0
538
+ b3 = 0
539
+ b4 = 0
540
+ b5 = 0
541
+ if b0 < 8
542
+ t0 = concat_shift (l4, l5, b0)
543
+ t1 = concat_shift (l5, l0, b0)
544
+ break
545
+ end
546
+ end
547
+ else
548
+ # Extract final 16 bytes at the first short read
549
+ t0 = concat_shift (l3, l4, b5)
550
+ t1 = concat_shift (l4, l5, b5)
551
+ break
552
+ end
553
+ else
554
+ t0 = concat_shift (l2, l3, b4)
555
+ t1 = concat_shift (l3, l4, b4)
556
+ break
557
+ end
558
+ else
559
+ t0 = concat_shift (l1, l2, b3)
560
+ t1 = concat_shift (l2, l3, b3)
561
+ break
562
+ end
563
+ else
564
+ t0 = concat_shift (l0, l1, b2)
565
+ t1 = concat_shift (l1, l2, b2)
566
+ break
567
+ end
568
+ else
569
+ t0 = concat_shift (l5, l0, b1)
570
+ t1 = concat_shift (l0, l1, b1)
571
+ break
572
+ end
573
+ end
574
+ end
575
+
576
+ # Partial chunk, handle based on size
577
+ bytes_chunk = b0 + b1 + b2 + b3 + b4 + b5
578
+ if buflen > 0
579
+ # Finalize last full chunk
580
+ seed ⊻= see1
581
+ seed ⊻= see2
582
+ end
583
+ buflen += bytes_chunk
584
+ if buflen ≤ 16
585
+ if bytes_chunk ≥ 0x4
586
+ seed ⊻= bytes_chunk
587
+ if bytes_chunk ≥ 0x8
588
+ a = l0
589
+ b = t1
590
+ else
591
+ a = UInt64 (l0 % UInt32)
592
+ b = UInt64 ((l0 >>> ((0x8 * (bytes_chunk - 0x4 )) % 0x3f )) % UInt32)
593
+ end
594
+ elseif bytes_chunk > 0x0
595
+ b0 = l0 % UInt8
596
+ b1 = (l0 >>> ((0x8 * div (bytes_chunk, 0x2 )) % 0x3f )) % UInt8
597
+ b2 = (l0 >>> ((0x8 * (bytes_chunk - 0x1 )) % 0x3f )) % UInt8
598
+ a = (UInt64 (b0) << 45 ) | UInt64 (b2)
599
+ b = UInt64 (b1)
600
+ end
601
+ else
602
+ if bytes_chunk > 0x10
603
+ seed = hash_mix (l0 ⊻ secret[3 ], l1 ⊻ seed)
604
+ if bytes_chunk > 0x20
605
+ seed = hash_mix (l2 ⊻ secret[3 ], l3 ⊻ seed)
606
+ end
607
+ end
608
+ a = t0 ⊻ bytes_chunk
609
+ b = t1
610
+ end
611
+
612
+ a = a ⊻ secret[2 ]
613
+ b = b ⊻ seed
614
+ b, a = mul_parts (a, b)
615
+ return hash_mix (a ⊻ secret[4 ], b ⊻ secret[2 ] ⊻ bytes_chunk)
616
+ end
617
+
347
618
@assume_effects :total hash (data:: String , h:: UInt ) =
348
619
GC. @preserve data hash_bytes (pointer (data), sizeof (data), UInt64 (h), HASH_SECRET) % UInt
349
620
0 commit comments