| ... | ... | @@ -522,8 +522,9 @@ pub fn getTrivialEqlFn(comptime K: type) (fn (K, K) bool) { |
| 522 | 522 | pub fn getAutoHashFn(comptime K: type) (fn (K) u32) { |
| 523 | 523 | return struct { |
| 524 | 524 | fn hash(key: K) u32 { |
| 525 | | const h = autoHash(key, 0); |
| 526 | | return @truncate(u32, h); |
| 525 | var hasher = Wyhash.init(0); |
| 526 | autoHash(&hasher, key); |
| 527 | return @truncate(u32, hasher.final()); |
| 527 | 528 | } |
| 528 | 529 | }.hash; |
| 529 | 530 | } |
| ... | ... | @@ -538,10 +539,7 @@ pub fn getAutoEqlFn(comptime K: type) (fn (K, K) bool) { |
| 538 | 539 | |
| 539 | 540 | /// Provides generic hashing for any eligible type. |
| 540 | 541 | /// Only hashes `key` itself, pointers are not followed. |
| 541 | | /// The underlying hashing algorithm is wyhash. |
| 542 | | pub fn autoHash(key: var, seed: u64) u64 { |
| 543 | | // We use the fact that wyhash takes an input seed to "chain" hasing when the |
| 544 | | // key has multiple parts that are not necessarily contiguous in memory. |
| 542 | pub fn autoHash(hasher: var, key: var) void { |
| 545 | 543 | const Key = @typeOf(key); |
| 546 | 544 | switch (@typeInfo(Key)) { |
| 547 | 545 | builtin.TypeId.NoReturn, |
| ... | ... | @@ -557,91 +555,101 @@ pub fn autoHash(key: var, seed: u64) u64 { |
| 557 | 555 | builtin.TypeId.EnumLiteral, |
| 558 | 556 | => @compileError("cannot hash this type"), |
| 559 | 557 | |
| 560 | | builtin.TypeId.Int => return Wyhash.hash(seed, std.mem.asBytes(&key)), |
| 558 | builtin.TypeId.Int => hasher.update(std.mem.asBytes(&key)), |
| 561 | 559 | |
| 562 | | builtin.TypeId.Float => |info| return autoHash(@bitCast(@IntType(false, info.bits), key), seed), |
| 560 | builtin.TypeId.Float => |info| autoHash(hasher, @bitCast(@IntType(false, info.bits), key)), |
| 563 | 561 | |
| 564 | | builtin.TypeId.Bool => return autoHash(@boolToInt(key), seed), |
| 565 | | builtin.TypeId.Enum => return autoHash(@enumToInt(key), seed), |
| 566 | | builtin.TypeId.ErrorSet => return autoHash(@errorToInt(key), seed), |
| 567 | | builtin.TypeId.Promise, builtin.TypeId.Fn => return autoHash(@ptrToInt(key), seed), |
| 562 | builtin.TypeId.Bool => autoHash(hasher, @boolToInt(key)), |
| 563 | builtin.TypeId.Enum => autoHash(hasher, @enumToInt(key)), |
| 564 | builtin.TypeId.ErrorSet => autoHash(hasher, @errorToInt(key)), |
| 565 | builtin.TypeId.Promise, builtin.TypeId.Fn => autoHash(hasher, @ptrToInt(key)), |
| 568 | 566 | |
| 569 | | builtin.TypeId.Pointer => |info| return switch (info.size) { |
| 567 | builtin.TypeId.Pointer => |info| switch (info.size) { |
| 570 | 568 | builtin.TypeInfo.Pointer.Size.One, |
| 571 | 569 | builtin.TypeInfo.Pointer.Size.Many, |
| 572 | 570 | builtin.TypeInfo.Pointer.Size.C, |
| 573 | | => return autoHash(@ptrToInt(key), seed), |
| 571 | => autoHash(hasher, @ptrToInt(key)), |
| 574 | 572 | |
| 575 | | builtin.TypeInfo.Pointer.Size.Slice => return autoHash(key.len, autoHash(key.ptr, seed)), |
| 573 | builtin.TypeInfo.Pointer.Size.Slice => { |
| 574 | autoHash(hasher, key.ptr); |
| 575 | autoHash(hasher, key.len); |
| 576 | }, |
| 576 | 577 | }, |
| 577 | 578 | |
| 578 | | builtin.TypeId.Optional => return if (key) |k| autoHash(k, seed) else 0, |
| 579 | builtin.TypeId.Optional => if (key) |k| autoHash(hasher, k), |
| 579 | 580 | |
| 580 | 581 | builtin.TypeId.Array => { |
| 581 | 582 | // TODO detect via a trait when Key has no padding bits to |
| 582 | 583 | // hash it as an array of bytes. |
| 583 | 584 | // Otherwise, hash every element. |
| 584 | | var s = seed; |
| 585 | 585 | for (key) |element| { |
| 586 | | // We reuse the hash of the previous element as the seed for the |
| 587 | | // next one so that they're dependant. |
| 588 | | s = autoHash(element, s); |
| 586 | autoHash(hasher, element); |
| 589 | 587 | } |
| 590 | | return s; |
| 591 | 588 | }, |
| 592 | 589 | |
| 593 | 590 | builtin.TypeId.Vector => |info| { |
| 594 | | // If there's no unused bits in the child type, we can just hash |
| 595 | | // this as an array of bytes. |
| 596 | 591 | if (info.child.bit_count % 8 == 0) { |
| 597 | | return Wyhash.hash(seed, mem.asBytes(&key)); |
| 598 | | } |
| 599 | | |
| 600 | | // Otherwise, hash every element. |
| 601 | | var s = seed; |
| 602 | | // TODO remove the copy to an array once field access is done. |
| 603 | | const array: [info.len]info.child = key; |
| 604 | | comptime var i: u32 = 0; |
| 605 | | inline while (i < info.len) : (i += 1) { |
| 606 | | s = autoHash(array[i], s); |
| 592 | // If there's no unused bits in the child type, we can just hash |
| 593 | // this as an array of bytes. |
| 594 | hasher.update(mem.asBytes(&key)); |
| 595 | } else { |
| 596 | // Otherwise, hash every element. |
| 597 | // TODO remove the copy to an array once field access is done. |
| 598 | const array: [info.len]info.child = key; |
| 599 | comptime var i: u32 = 0; |
| 600 | inline while (i < info.len) : (i += 1) { |
| 601 | autoHash(hasher, array[i]); |
| 602 | } |
| 607 | 603 | } |
| 608 | | return s; |
| 609 | 604 | }, |
| 610 | 605 | |
| 611 | 606 | builtin.TypeId.Struct => |info| { |
| 612 | 607 | // TODO detect via a trait when Key has no padding bits to |
| 613 | 608 | // hash it as an array of bytes. |
| 614 | 609 | // Otherwise, hash every field. |
| 615 | | var s = seed; |
| 616 | 610 | inline for (info.fields) |field| { |
| 617 | 611 | // We reuse the hash of the previous field as the seed for the |
| 618 | 612 | // next one so that they're dependant. |
| 619 | | s = autoHash(@field(key, field.name), s); |
| 613 | autoHash(hasher, @field(key, field.name)); |
| 620 | 614 | } |
| 621 | | return s; |
| 622 | 615 | }, |
| 623 | 616 | |
| 624 | | builtin.TypeId.Union => |info| { |
| 617 | builtin.TypeId.Union => |info| blk: { |
| 625 | 618 | if (info.tag_type) |tag_type| { |
| 626 | 619 | const tag = meta.activeTag(key); |
| 627 | | const s = autoHash(tag, seed); |
| 620 | const s = autoHash(hasher, tag); |
| 628 | 621 | inline for (info.fields) |field| { |
| 629 | 622 | const enum_field = field.enum_field.?; |
| 630 | 623 | if (enum_field.value == @enumToInt(tag)) { |
| 631 | | return autoHash(@field(key, enum_field.name), s); |
| 624 | autoHash(hasher, @field(key, enum_field.name)); |
| 625 | // TODO use a labelled break when it does not crash the compiler. |
| 626 | // break :blk; |
| 627 | return; |
| 632 | 628 | } |
| 633 | 629 | } |
| 634 | 630 | unreachable; |
| 635 | 631 | } else @compileError("cannot hash untagged union type: " ++ @typeName(Key) ++ ", provide your own hash function"); |
| 636 | 632 | }, |
| 637 | 633 | |
| 638 | | builtin.TypeId.ErrorUnion => { |
| 639 | | return autoHash(key catch |err| return autoHash(err, seed), seed); |
| 634 | builtin.TypeId.ErrorUnion => blk: { |
| 635 | const payload = key catch |err| { |
| 636 | autoHash(hasher, err); |
| 637 | break :blk; |
| 638 | }; |
| 639 | autoHash(hasher, payload); |
| 640 | 640 | }, |
| 641 | 641 | } |
| 642 | 642 | } |
| 643 | 643 | |
| 644 | fn testAutoHash(key: var) u64 { |
| 645 | var hasher = Wyhash.init(0); |
| 646 | autoHash(&hasher, key); |
| 647 | return hasher.final(); |
| 648 | } |
| 649 | |
| 644 | 650 | test "autoHash slice" { |
| 651 | // Allocate one array dynamically so that we're assured it is not merged |
| 652 | // with the other by the optimization passes. |
| 645 | 653 | const array1 = try std.heap.direct_allocator.create([6]u32); |
| 646 | 654 | defer std.heap.direct_allocator.destroy(array1); |
| 647 | 655 | array1.* = [_]u32{ 1, 2, 3, 4, 5, 6 }; |
| ... | ... | @@ -649,38 +657,46 @@ test "autoHash slice" { |
| 649 | 657 | const a = array1[0..]; |
| 650 | 658 | const b = array2[0..]; |
| 651 | 659 | const c = array1[0..3]; |
| 652 | | testing.expect(autoHash(a, 0) == autoHash(a, 0)); |
| 653 | | testing.expect(autoHash(a, 0) != autoHash(array1, 0)); |
| 654 | | testing.expect(autoHash(a, 0) != autoHash(b, 0)); |
| 655 | | testing.expect(autoHash(a, 0) != autoHash(c, 0)); |
| 660 | testing.expect(testAutoHash(a) == testAutoHash(a)); |
| 661 | testing.expect(testAutoHash(a) != testAutoHash(array1)); |
| 662 | testing.expect(testAutoHash(a) != testAutoHash(b)); |
| 663 | testing.expect(testAutoHash(a) != testAutoHash(c)); |
| 656 | 664 | } |
| 657 | 665 | |
| 658 | | test "autoHash optional" { |
| 666 | test "testAutoHash optional" { |
| 659 | 667 | const a: ?u32 = 123; |
| 660 | 668 | const b: ?u32 = null; |
| 661 | | testing.expectEqual(autoHash(a, 0), autoHash(u32(123), 0)); |
| 662 | | testing.expect(autoHash(a, 0) != autoHash(b, 0)); |
| 663 | | testing.expectEqual(autoHash(b, 0), 0); |
| 669 | testing.expectEqual(testAutoHash(a), testAutoHash(u32(123))); |
| 670 | testing.expect(testAutoHash(a) != testAutoHash(b)); |
| 671 | testing.expectEqual(testAutoHash(b), 0); |
| 664 | 672 | } |
| 665 | 673 | |
| 666 | | test "autoHash array" { |
| 674 | test "testAutoHash array" { |
| 667 | 675 | const a = [_]u32{ 1, 2, 3 }; |
| 668 | | const h = autoHash(a, 0); |
| 669 | | testing.expectEqual(h, autoHash(u32(3), autoHash(u32(2), autoHash(u32(1), 0)))); |
| 676 | const h = testAutoHash(a); |
| 677 | var hasher = Wyhash.init(0); |
| 678 | autoHash(&hasher, u32(1)); |
| 679 | autoHash(&hasher, u32(2)); |
| 680 | autoHash(&hasher, u32(3)); |
| 681 | testing.expectEqual(h, hasher.final()); |
| 670 | 682 | } |
| 671 | 683 | |
| 672 | | test "autoHash struct" { |
| 684 | test "testAutoHash struct" { |
| 673 | 685 | const Foo = struct { |
| 674 | 686 | a: u32 = 1, |
| 675 | 687 | b: u32 = 2, |
| 676 | 688 | c: u32 = 3, |
| 677 | 689 | }; |
| 678 | 690 | const f = Foo{}; |
| 679 | | const h = autoHash(f, 0); |
| 680 | | testing.expectEqual(h, autoHash(u32(3), autoHash(u32(2), autoHash(u32(1), 0)))); |
| 691 | const h = testAutoHash(f); |
| 692 | var hasher = Wyhash.init(0); |
| 693 | autoHash(&hasher, u32(1)); |
| 694 | autoHash(&hasher, u32(2)); |
| 695 | autoHash(&hasher, u32(3)); |
| 696 | testing.expectEqual(h, hasher.final()); |
| 681 | 697 | } |
| 682 | 698 | |
| 683 | | test "autoHash union" { |
| 699 | test "testAutoHash union" { |
| 684 | 700 | const Foo = union(enum) { |
| 685 | 701 | A: u32, |
| 686 | 702 | B: f32, |
| ... | ... | @@ -690,24 +706,24 @@ test "autoHash union" { |
| 690 | 706 | const a = Foo{ .A = 18 }; |
| 691 | 707 | var b = Foo{ .B = 12.34 }; |
| 692 | 708 | const c = Foo{ .C = 18 }; |
| 693 | | testing.expect(autoHash(a, 0) == autoHash(a, 0)); |
| 694 | | testing.expect(autoHash(a, 0) != autoHash(b, 0)); |
| 695 | | testing.expect(autoHash(a, 0) != autoHash(c, 0)); |
| 709 | testing.expect(testAutoHash(a) == testAutoHash(a)); |
| 710 | testing.expect(testAutoHash(a) != testAutoHash(b)); |
| 711 | testing.expect(testAutoHash(a) != testAutoHash(c)); |
| 696 | 712 | |
| 697 | 713 | b = Foo{ .A = 18 }; |
| 698 | | testing.expect(autoHash(a, 0) == autoHash(b, 0)); |
| 714 | testing.expect(testAutoHash(a) == testAutoHash(b)); |
| 699 | 715 | } |
| 700 | 716 | |
| 701 | | test "autoHash vector" { |
| 717 | test "testAutoHash vector" { |
| 702 | 718 | const a: @Vector(4, u32) = [_]u32{ 1, 2, 3, 4 }; |
| 703 | 719 | const b: @Vector(4, u32) = [_]u32{ 1, 2, 3, 5 }; |
| 704 | 720 | const c: @Vector(4, u31) = [_]u31{ 1, 2, 3, 4 }; |
| 705 | | testing.expect(autoHash(a, 0) == autoHash(a, 0)); |
| 706 | | testing.expect(autoHash(a, 0) != autoHash(b, 0)); |
| 707 | | testing.expect(autoHash(a, 0) != autoHash(c, 0)); |
| 721 | testing.expect(testAutoHash(a) == testAutoHash(a)); |
| 722 | testing.expect(testAutoHash(a) != testAutoHash(b)); |
| 723 | testing.expect(testAutoHash(a) != testAutoHash(c)); |
| 708 | 724 | } |
| 709 | 725 | |
| 710 | | test "autoHash error union" { |
| 726 | test "testAutoHash error union" { |
| 711 | 727 | const Errors = error{Test}; |
| 712 | 728 | const Foo = struct { |
| 713 | 729 | a: u32 = 1, |
| ... | ... | @@ -716,7 +732,7 @@ test "autoHash error union" { |
| 716 | 732 | }; |
| 717 | 733 | const f = Foo{}; |
| 718 | 734 | const g: Errors!Foo = Errors.Test; |
| 719 | | testing.expect(autoHash(f, 0) != autoHash(g, 0)); |
| 720 | | testing.expect(autoHash(f, 0) == autoHash(Foo{}, 0)); |
| 721 | | testing.expect(autoHash(g, 0) == autoHash(Errors.Test, 0)); |
| 735 | testing.expect(testAutoHash(f) != testAutoHash(g)); |
| 736 | testing.expect(testAutoHash(f) == testAutoHash(Foo{})); |
| 737 | testing.expect(testAutoHash(g) == testAutoHash(Errors.Test)); |
| 722 | 738 | } |