authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2023-06-02 17:34:12-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2023-06-10 20:51:09-07:00
logab86b2024883f67c0fa06108f66e4e88b98c3163
tree137536a6bf4d5c089992a1c38604e5c9f1d9b551
parent7c12e064c4e6cd7ea2243a665984e5c49bc94229

std.hash: improve small-key hashing in Wyhash

Instead of carrying an optimized version of wyhash in the compiler for small keys, put it into the std lib where it belongs. ...except it does not match the official test cases. This will need to be fixed before merging into master. This is an extremely contributor-friendly task. Related issue: #15916

2 files changed, 22 insertions(+), 107 deletions(-)

lib/std/hash/benchmark.zig-10
......@@ -38,16 +38,6 @@ const hashes = [_]Hash{
3838 .name = "wyhash",
3939 .init_u64 = 0,
4040 },
41 Hash{
42 .ty = hash.XxHash64,
43 .name = "xxhash64",
44 .init_u64 = 0,
45 },
46 Hash{
47 .ty = hash.XxHash32,
48 .name = "xxhash32",
49 .init_u64 = 0,
50 },
5141 Hash{
5242 .ty = hash.Fnv1a_64,
5343 .name = "fnv1a",
src/InternPool.zig+22-97
......@@ -69,6 +69,7 @@ const assert = std.debug.assert;
6969const BigIntConst = std.math.big.int.Const;
7070const BigIntMutable = std.math.big.int.Mutable;
7171const Limb = std.math.big.Limb;
72const Hash = std.hash.Wyhash;
7273
7374const InternPool = @This();
7475const Module = @import("Module.zig");
......@@ -675,34 +676,34 @@ pub const Key = union(enum) {
675676 .empty_enum_value,
676677 .inferred_error_set_type,
677678 .un,
678 => |x| WyhashKing.hash(seed, asBytes(&x)),
679 => |x| Hash.hash(seed, asBytes(&x)),
679680
680 .int_type => |x| WyhashKing.hash(seed + @enumToInt(x.signedness), asBytes(&x.bits)),
681 .union_type => |x| WyhashKing.hash(seed + @enumToInt(x.runtime_tag), asBytes(&x.index)),
681 .int_type => |x| Hash.hash(seed + @enumToInt(x.signedness), asBytes(&x.bits)),
682 .union_type => |x| Hash.hash(seed + @enumToInt(x.runtime_tag), asBytes(&x.index)),
682683
683684 .error_union => |x| switch (x.val) {
684 .err_name => |y| WyhashKing.hash(seed + 0, asBytes(&x.ty) ++ asBytes(&y)),
685 .payload => |y| WyhashKing.hash(seed + 1, asBytes(&x.ty) ++ asBytes(&y)),
685 .err_name => |y| Hash.hash(seed + 0, asBytes(&x.ty) ++ asBytes(&y)),
686 .payload => |y| Hash.hash(seed + 1, asBytes(&x.ty) ++ asBytes(&y)),
686687 },
687688
688 .runtime_value => |x| WyhashKing.hash(seed, asBytes(&x.val)),
689 .opaque_type => |x| WyhashKing.hash(seed, asBytes(&x.decl)),
689 .runtime_value => |x| Hash.hash(seed, asBytes(&x.val)),
690 .opaque_type => |x| Hash.hash(seed, asBytes(&x.decl)),
690691
691692 .enum_type => |enum_type| {
692 var hasher = std.hash.Wyhash.init(seed);
693 var hasher = Hash.init(seed);
693694 std.hash.autoHash(&hasher, enum_type.decl);
694695 return hasher.final();
695696 },
696697
697698 .variable => |variable| {
698 var hasher = std.hash.Wyhash.init(seed);
699 var hasher = Hash.init(seed);
699700 std.hash.autoHash(&hasher, variable.decl);
700701 return hasher.final();
701702 },
702 .extern_func => |x| WyhashKing.hash(seed, asBytes(&x.ty) ++ asBytes(&x.decl)),
703 .extern_func => |x| Hash.hash(seed, asBytes(&x.ty) ++ asBytes(&x.decl)),
703704
704705 .int => |int| {
705 var hasher = std.hash.Wyhash.init(seed);
706 var hasher = Hash.init(seed);
706707 // Canonicalize all integers by converting them to BigIntConst.
707708 switch (int.storage) {
708709 .u64, .i64, .big_int => {
......@@ -725,7 +726,7 @@ pub const Key = union(enum) {
725726 },
726727
727728 .float => |float| {
728 var hasher = std.hash.Wyhash.init(seed);
729 var hasher = Hash.init(seed);
729730 std.hash.autoHash(&hasher, float.ty);
730731 switch (float.storage) {
731732 inline else => |val| std.hash.autoHash(
......@@ -743,19 +744,19 @@ pub const Key = union(enum) {
743744 const seed2 = seed + @enumToInt(addr);
744745 const common = asBytes(&ptr.ty) ++ asBytes(&ptr.len);
745746 return switch (ptr.addr) {
746 .decl => |x| WyhashKing.hash(seed2, common ++ asBytes(&x)),
747 .decl => |x| Hash.hash(seed2, common ++ asBytes(&x)),
747748
748 .mut_decl => |x| WyhashKing.hash(
749 .mut_decl => |x| Hash.hash(
749750 seed2,
750751 asBytes(&x.decl) ++ asBytes(&x.runtime_index),
751752 ),
752753
753 .int, .eu_payload, .opt_payload, .comptime_field => |int| WyhashKing.hash(
754 .int, .eu_payload, .opt_payload, .comptime_field => |int| Hash.hash(
754755 seed2,
755756 asBytes(&int),
756757 ),
757758
758 .elem, .field => |x| WyhashKing.hash(
759 .elem, .field => |x| Hash.hash(
759760 seed2,
760761 asBytes(&x.base) ++ asBytes(&x.index),
761762 ),
......@@ -763,7 +764,7 @@ pub const Key = union(enum) {
763764 },
764765
765766 .aggregate => |aggregate| {
766 var hasher = std.hash.Wyhash.init(seed);
767 var hasher = Hash.init(seed);
767768 std.hash.autoHash(&hasher, aggregate.ty);
768769 const len = ip.aggregateTypeLen(aggregate.ty);
769770 const child = switch (ip.indexToKey(aggregate.ty)) {
......@@ -823,13 +824,13 @@ pub const Key = union(enum) {
823824 },
824825
825826 .error_set_type => |error_set_type| {
826 var hasher = std.hash.Wyhash.init(seed);
827 var hasher = Hash.init(seed);
827828 for (error_set_type.names) |elem| std.hash.autoHash(&hasher, elem);
828829 return hasher.final();
829830 },
830831
831832 .anon_struct_type => |anon_struct_type| {
832 var hasher = std.hash.Wyhash.init(seed);
833 var hasher = Hash.init(seed);
833834 for (anon_struct_type.types) |elem| std.hash.autoHash(&hasher, elem);
834835 for (anon_struct_type.values) |elem| std.hash.autoHash(&hasher, elem);
835836 for (anon_struct_type.names) |elem| std.hash.autoHash(&hasher, elem);
......@@ -837,7 +838,7 @@ pub const Key = union(enum) {
837838 },
838839
839840 .func_type => |func_type| {
840 var hasher = std.hash.Wyhash.init(seed);
841 var hasher = Hash.init(seed);
841842 for (func_type.param_types) |param_type| std.hash.autoHash(&hasher, param_type);
842843 std.hash.autoHash(&hasher, func_type.return_type);
843844 std.hash.autoHash(&hasher, func_type.comptime_bits);
......@@ -851,7 +852,7 @@ pub const Key = union(enum) {
851852 },
852853
853854 .memoized_call => |memoized_call| {
854 var hasher = std.hash.Wyhash.init(seed);
855 var hasher = Hash.init(seed);
855856 std.hash.autoHash(&hasher, memoized_call.func);
856857 for (memoized_call.arg_values) |arg| std.hash.autoHash(&hasher, arg);
857858 return hasher.final();
......@@ -5744,79 +5745,3 @@ pub fn zigTypeTagOrPoison(ip: *const InternPool, index: Index) error{GenericPois
57445745 .none => unreachable, // special tag
57455746 };
57465747}
5747
5748/// I got this from King, using this temporarily until std lib hashing can be
5749/// improved to make stateless hashing performant. Currently the
5750/// implementations suffer from not special casing small lengths and not taking
5751/// advantage of comptime-known lengths, both of which this implementation
5752/// does.
5753const WyhashKing = struct {
5754 inline fn mum(pair: *[2]u64) void {
5755 const x = @as(u128, pair[0]) *% pair[1];
5756 pair[0] = @truncate(u64, x);
5757 pair[1] = @truncate(u64, x >> 64);
5758 }
5759
5760 inline fn mix(a: u64, b: u64) u64 {
5761 var pair = [_]u64{ a, b };
5762 mum(&pair);
5763 return pair[0] ^ pair[1];
5764 }
5765
5766 inline fn read(comptime I: type, in: []const u8) I {
5767 return std.mem.readIntLittle(I, in[0..@sizeOf(I)]);
5768 }
5769
5770 const secret = [_]u64{
5771 0xa0761d6478bd642f,
5772 0xe7037ed1a0b428db,
5773 0x8ebc6af09c88c6e3,
5774 0x589965cc75374cc3,
5775 };
5776
5777 fn hash(seed: u64, input: anytype) u64 {
5778 var in: []const u8 = input;
5779 var last = std.mem.zeroes([2]u64);
5780 const starting_len: u64 = input.len;
5781 var state = seed ^ mix(seed ^ secret[0], secret[1]);
5782
5783 if (in.len <= 16) {
5784 if (in.len >= 4) {
5785 const end = (in.len >> 3) << 2;
5786 last[0] = (@as(u64, read(u32, in)) << 32) | read(u32, in[end..]);
5787 last[1] = (@as(u64, read(u32, in[in.len - 4 ..])) << 32) | read(u32, in[in.len - 4 - end ..]);
5788 } else if (in.len > 0) {
5789 last[0] = (@as(u64, in[0]) << 16) | (@as(u64, in[in.len >> 1]) << 8) | in[in.len - 1];
5790 }
5791 } else {
5792 large: {
5793 if (in.len <= 48) break :large;
5794 var split = [_]u64{ state, state, state };
5795 while (true) {
5796 for (&split, 0..) |*lane, i| {
5797 const a = read(u64, in[(i * 2) * 8 ..]) ^ secret[i + 1];
5798 const b = read(u64, in[((i * 2) + 1) * 8 ..]) ^ lane.*;
5799 lane.* = mix(a, b);
5800 }
5801 in = in[48..];
5802 if (in.len > 48) continue;
5803 state = split[0] ^ (split[1] ^ split[2]);
5804 break :large;
5805 }
5806 }
5807 while (true) {
5808 if (in.len <= 16) break;
5809 state = mix(read(u64, in) ^ secret[1], read(u64, in[8..]) ^ state);
5810 in = in[16..];
5811 if (in.len <= 16) break;
5812 }
5813 last[0] = read(u64, in[in.len - 16 ..]);
5814 last[1] = read(u64, in[in.len - 8 ..]);
5815 }
5816
5817 last[0] ^= secret[1];
5818 last[1] ^= state;
5819 mum(&last);
5820 return mix(last[0] ^ secret[0] ^ starting_len, last[1] ^ secret[1]);
5821 }
5822};