|
1 | 1 | # This file is a part of Julia. License is MIT: https://julialang.org/license
|
2 | 2 |
|
3 | 3 | const HASH_SEED = UInt == UInt64 ? 0xbdd89aa982704029 : 0xeabe9406
|
4 |
| -const HASH_SECRET = tuple( |
| 4 | +const HASH_SECRET = ( |
5 | 5 | 0x2d358dccaa6c78a5,
|
6 | 6 | 0x8bb84b93962eacc9,
|
7 | 7 | 0x4b33a62ed433d4a3,
|
@@ -73,7 +73,7 @@ hash(x::Union{Bool, Int8, UInt8, Int16, UInt16, Int32, UInt32}, h::UInt) = hash(
|
73 | 73 | hash_integer(x::Integer, h::UInt) = _hash_integer(x, UInt64(h)) % UInt
|
74 | 74 | function _hash_integer(
|
75 | 75 | x::Integer,
|
76 |
| - seed::UInt64 = HASH_SEED, |
| 76 | + seed::UInt64, |
77 | 77 | secret::NTuple{4, UInt64} = HASH_SECRET
|
78 | 78 | )
|
79 | 79 | seed ⊻= (x < 0)
|
@@ -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