| ... | @@ -4,6 +4,8 @@ const assert = debug.assert; | ... | @@ -4,6 +4,8 @@ const assert = debug.assert; |
| 4 | const testing = std.testing; | 4 | const testing = std.testing; |
| 5 | const math = std.math; | 5 | const math = std.math; |
| 6 | const mem = std.mem; | 6 | const mem = std.mem; |
| | 7 | const meta = std.meta; |
| | 8 | const wyhash = std.hash.wyhash; |
| 7 | const Allocator = mem.Allocator; | 9 | const Allocator = mem.Allocator; |
| 8 | const builtin = @import("builtin"); | 10 | const builtin = @import("builtin"); |
| 9 | | 11 | |
| ... | @@ -448,15 +450,17 @@ test "iterator hash map" { | ... | @@ -448,15 +450,17 @@ test "iterator hash map" { |
| 448 | try reset_map.putNoClobber(2, 22); | 450 | try reset_map.putNoClobber(2, 22); |
| 449 | try reset_map.putNoClobber(3, 33); | 451 | try reset_map.putNoClobber(3, 33); |
| 450 | | 452 | |
| | 453 | // TODO this test depends on the hashing algorithm, because it assumes the |
| | 454 | // order of the elements in the hashmap. This should not be the case. |
| 451 | var keys = [_]i32{ | 455 | var keys = [_]i32{ |
| | 456 | 1, |
| 452 | 3, | 457 | 3, |
| 453 | 2, | 458 | 2, |
| 454 | 1, | | |
| 455 | }; | 459 | }; |
| 456 | var values = [_]i32{ | 460 | var values = [_]i32{ |
| | 461 | 11, |
| 457 | 33, | 462 | 33, |
| 458 | 22, | 463 | 22, |
| 459 | 11, | | |
| 460 | }; | 464 | }; |
| 461 | | 465 | |
| 462 | var it = reset_map.iterator(); | 466 | var it = reset_map.iterator(); |
| ... | @@ -518,8 +522,8 @@ pub fn getTrivialEqlFn(comptime K: type) (fn (K, K) bool) { | ... | @@ -518,8 +522,8 @@ pub fn getTrivialEqlFn(comptime K: type) (fn (K, K) bool) { |
| 518 | pub fn getAutoHashFn(comptime K: type) (fn (K) u32) { | 522 | pub fn getAutoHashFn(comptime K: type) (fn (K) u32) { |
| 519 | return struct { | 523 | return struct { |
| 520 | fn hash(key: K) u32 { | 524 | fn hash(key: K) u32 { |
| 521 | comptime var rng = comptime std.rand.DefaultPrng.init(0); | 525 | const h = autoHash(key, 0); |
| 522 | return autoHash(key, &rng.random, u32); | 526 | return @truncate(u32, h); |
| 523 | } | 527 | } |
| 524 | }.hash; | 528 | }.hash; |
| 525 | } | 529 | } |
| ... | @@ -527,114 +531,192 @@ pub fn getAutoHashFn(comptime K: type) (fn (K) u32) { | ... | @@ -527,114 +531,192 @@ pub fn getAutoHashFn(comptime K: type) (fn (K) u32) { |
| 527 | pub fn getAutoEqlFn(comptime K: type) (fn (K, K) bool) { | 531 | pub fn getAutoEqlFn(comptime K: type) (fn (K, K) bool) { |
| 528 | return struct { | 532 | return struct { |
| 529 | fn eql(a: K, b: K) bool { | 533 | fn eql(a: K, b: K) bool { |
| 530 | return autoEql(a, b); | 534 | return meta.eql(a, b); |
| 531 | } | 535 | } |
| 532 | }.eql; | 536 | }.eql; |
| 533 | } | 537 | } |
| 534 | | 538 | |
| 535 | // TODO improve these hash functions | 539 | /// Provides generic hashing for any eligible type. |
| 536 | pub fn autoHash(key: var, comptime rng: *std.rand.Random, comptime HashInt: type) HashInt { | 540 | /// Only hashes `key` itself, pointers are not followed. |
| 537 | switch (@typeInfo(@typeOf(key))) { | 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. |
| | 545 | const Key = @typeOf(key); |
| | 546 | switch (@typeInfo(Key)) { |
| 538 | builtin.TypeId.NoReturn, | 547 | builtin.TypeId.NoReturn, |
| 539 | builtin.TypeId.Opaque, | 548 | builtin.TypeId.Opaque, |
| 540 | builtin.TypeId.Undefined, | 549 | builtin.TypeId.Undefined, |
| 541 | builtin.TypeId.ArgTuple, | 550 | builtin.TypeId.ArgTuple, |
| 542 | => @compileError("cannot hash this type"), | | |
| 543 | | | |
| 544 | builtin.TypeId.Void, | 551 | builtin.TypeId.Void, |
| 545 | builtin.TypeId.Null, | 552 | builtin.TypeId.Null, |
| 546 | => return 0, | | |
| 547 | | | |
| 548 | builtin.TypeId.Int => |info| { | | |
| 549 | const unsigned_x = @bitCast(@IntType(false, info.bits), key); | | |
| 550 | if (info.bits <= HashInt.bit_count) { | | |
| 551 | return HashInt(unsigned_x) ^ comptime rng.scalar(HashInt); | | |
| 552 | } else { | | |
| 553 | return @truncate(HashInt, unsigned_x ^ comptime rng.scalar(@typeOf(unsigned_x))); | | |
| 554 | } | | |
| 555 | }, | | |
| 556 | | | |
| 557 | builtin.TypeId.Float => |info| { | | |
| 558 | return autoHash(@bitCast(@IntType(false, info.bits), key), rng, HashInt); | | |
| 559 | }, | | |
| 560 | builtin.TypeId.Bool => return autoHash(@boolToInt(key), rng, HashInt), | | |
| 561 | builtin.TypeId.Enum => return autoHash(@enumToInt(key), rng, HashInt), | | |
| 562 | builtin.TypeId.ErrorSet => return autoHash(@errorToInt(key), rng, HashInt), | | |
| 563 | builtin.TypeId.Promise, builtin.TypeId.Fn => return autoHash(@ptrToInt(key), rng, HashInt), | | |
| 564 | | | |
| 565 | builtin.TypeId.BoundFn, | 553 | builtin.TypeId.BoundFn, |
| 566 | builtin.TypeId.ComptimeFloat, | 554 | builtin.TypeId.ComptimeFloat, |
| 567 | builtin.TypeId.ComptimeInt, | 555 | builtin.TypeId.ComptimeInt, |
| 568 | builtin.TypeId.Type, | 556 | builtin.TypeId.Type, |
| 569 | builtin.TypeId.EnumLiteral, | 557 | builtin.TypeId.EnumLiteral, |
| 570 | => return 0, | 558 | => @compileError("cannot hash this type"), |
| 571 | | 559 | |
| 572 | builtin.TypeId.Pointer => |info| switch (info.size) { | 560 | builtin.TypeId.Int => return wyhash(std.mem.asBytes(&key), seed), |
| 573 | builtin.TypeInfo.Pointer.Size.One => @compileError("TODO auto hash for single item pointers"), | 561 | |
| 574 | builtin.TypeInfo.Pointer.Size.Many => @compileError("TODO auto hash for many item pointers"), | 562 | builtin.TypeId.Float => |info| return autoHash(@bitCast(@IntType(false, info.bits), key), seed), |
| 575 | builtin.TypeInfo.Pointer.Size.C => @compileError("TODO auto hash C pointers"), | 563 | |
| 576 | builtin.TypeInfo.Pointer.Size.Slice => { | 564 | builtin.TypeId.Bool => return autoHash(@boolToInt(key), seed), |
| 577 | const interval = std.math.max(1, key.len / 256); | 565 | builtin.TypeId.Enum => return autoHash(@enumToInt(key), seed), |
| 578 | var i: usize = 0; | 566 | builtin.TypeId.ErrorSet => return autoHash(@errorToInt(key), seed), |
| 579 | var h = comptime rng.scalar(HashInt); | 567 | builtin.TypeId.Promise, builtin.TypeId.Fn => return autoHash(@ptrToInt(key), seed), |
| 580 | while (i < key.len) : (i += interval) { | 568 | |
| 581 | h ^= autoHash(key[i], rng, HashInt); | 569 | builtin.TypeId.Pointer => |info| return switch (info.size) { |
| 582 | } | 570 | builtin.TypeInfo.Pointer.Size.One, |
| 583 | return h; | 571 | builtin.TypeInfo.Pointer.Size.Many, |
| 584 | }, | 572 | builtin.TypeInfo.Pointer.Size.C, |
| | 573 | => return autoHash(@ptrToInt(key), seed), |
| | 574 | |
| | 575 | builtin.TypeInfo.Pointer.Size.Slice => return autoHash(key.len, autoHash(key.ptr, seed)), |
| 585 | }, | 576 | }, |
| 586 | | 577 | |
| 587 | builtin.TypeId.Optional => @compileError("TODO auto hash for optionals"), | 578 | builtin.TypeId.Optional => return if (key) |k| autoHash(k, seed) else 0, |
| 588 | builtin.TypeId.Array => @compileError("TODO auto hash for arrays"), | 579 | |
| 589 | builtin.TypeId.Vector => @compileError("TODO auto hash for vectors"), | 580 | builtin.TypeId.Array => { |
| 590 | builtin.TypeId.Struct => @compileError("TODO auto hash for structs"), | 581 | // TODO detect via a trait when Key has no padding bits to |
| 591 | builtin.TypeId.Union => @compileError("TODO auto hash for unions"), | 582 | // hash it as an array of bytes. |
| 592 | builtin.TypeId.ErrorUnion => @compileError("TODO auto hash for unions"), | 583 | // Otherwise, hash every element. |
| 593 | } | 584 | var s = seed; |
| 594 | } | 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); |
| | 589 | } |
| | 590 | return s; |
| | 591 | }, |
| 595 | | 592 | |
| 596 | pub fn autoEql(a: var, b: @typeOf(a)) bool { | 593 | builtin.TypeId.Vector => |info| { |
| 597 | switch (@typeInfo(@typeOf(a))) { | 594 | // If there's no unused bits in the child type, we can just hash |
| 598 | builtin.TypeId.NoReturn, | 595 | // this as an array of bytes. |
| 599 | builtin.TypeId.Opaque, | 596 | if (info.child.bit_count % 8 == 0) { |
| 600 | builtin.TypeId.Undefined, | 597 | return wyhash(mem.asBytes(&key), seed); |
| 601 | builtin.TypeId.ArgTuple, | 598 | } |
| 602 | => @compileError("cannot test equality of this type"), | 599 | |
| 603 | builtin.TypeId.Void, | 600 | // Otherwise, hash every element. |
| 604 | builtin.TypeId.Null, | 601 | var s = seed; |
| 605 | => return true, | 602 | // TODO remove the copy to an array once field access is done. |
| 606 | builtin.TypeId.Bool, | 603 | const array: [info.len]info.child = key; |
| 607 | builtin.TypeId.Int, | 604 | comptime var i: u32 = 0; |
| 608 | builtin.TypeId.Float, | 605 | inline while (i < info.len) : (i += 1) { |
| 609 | builtin.TypeId.ComptimeFloat, | 606 | s = autoHash(array[i], s); |
| 610 | builtin.TypeId.ComptimeInt, | 607 | } |
| 611 | builtin.TypeId.EnumLiteral, | 608 | return s; |
| 612 | builtin.TypeId.Promise, | 609 | }, |
| 613 | builtin.TypeId.Enum, | 610 | |
| 614 | builtin.TypeId.BoundFn, | 611 | builtin.TypeId.Struct => |info| { |
| 615 | builtin.TypeId.Fn, | 612 | // TODO detect via a trait when Key has no padding bits to |
| 616 | builtin.TypeId.ErrorSet, | 613 | // hash it as an array of bytes. |
| 617 | builtin.TypeId.Type, | 614 | // Otherwise, hash every field. |
| 618 | => return a == b, | 615 | var s = seed; |
| 619 | | 616 | inline for (info.fields) |field| { |
| 620 | builtin.TypeId.Pointer => |info| switch (info.size) { | 617 | // We reuse the hash of the previous field as the seed for the |
| 621 | builtin.TypeInfo.Pointer.Size.One => @compileError("TODO auto eql for single item pointers"), | 618 | // next one so that they're dependant. |
| 622 | builtin.TypeInfo.Pointer.Size.Many => @compileError("TODO auto eql for many item pointers"), | 619 | s = autoHash(@field(key, field.name), s); |
| 623 | builtin.TypeInfo.Pointer.Size.C => @compileError("TODO auto eql for C pointers"), | 620 | } |
| 624 | builtin.TypeInfo.Pointer.Size.Slice => { | 621 | return s; |
| 625 | if (a.len != b.len) return false; | 622 | }, |
| 626 | for (a) |a_item, i| { | 623 | |
| 627 | if (!autoEql(a_item, b[i])) return false; | 624 | builtin.TypeId.Union => |info| { |
| | 625 | if (info.tag_type) |tag_type| { |
| | 626 | const tag = meta.activeTag(key); |
| | 627 | const s = autoHash(tag, seed); |
| | 628 | inline for (info.fields) |field| { |
| | 629 | const enum_field = field.enum_field.?; |
| | 630 | if (enum_field.value == @enumToInt(tag)) { |
| | 631 | return autoHash(@field(key, enum_field.name), s); |
| | 632 | } |
| 628 | } | 633 | } |
| 629 | return true; | 634 | unreachable; |
| 630 | }, | 635 | } else @compileError("cannot hash untagged union type: " ++ @typeName(Key) ++ ", provide your own hash function"); |
| 631 | }, | 636 | }, |
| 632 | | 637 | |
| 633 | builtin.TypeId.Optional => @compileError("TODO auto eql for optionals"), | 638 | builtin.TypeId.ErrorUnion => { |
| 634 | builtin.TypeId.Array => @compileError("TODO auto eql for arrays"), | 639 | return autoHash(key catch |err| return autoHash(err, seed), seed); |
| 635 | builtin.TypeId.Struct => @compileError("TODO auto eql for structs"), | 640 | }, |
| 636 | builtin.TypeId.Union => @compileError("TODO auto eql for unions"), | | |
| 637 | builtin.TypeId.ErrorUnion => @compileError("TODO auto eql for unions"), | | |
| 638 | builtin.TypeId.Vector => @compileError("TODO auto eql for vectors"), | | |
| 639 | } | 641 | } |
| 640 | } | 642 | } |
| | 643 | |
| | 644 | test "autoHash slice" { |
| | 645 | const array1 = try std.heap.direct_allocator.create([6]u32); |
| | 646 | defer std.heap.direct_allocator.destroy(array1); |
| | 647 | array1.* = [_]u32{ 1, 2, 3, 4, 5, 6 }; |
| | 648 | const array2 = [_]u32{ 1, 2, 3, 4, 5, 6 }; |
| | 649 | const a = array1[0..]; |
| | 650 | const b = array2[0..]; |
| | 651 | 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)); |
| | 656 | } |
| | 657 | |
| | 658 | test "autoHash optional" { |
| | 659 | const a: ?u32 = 123; |
| | 660 | 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); |
| | 664 | } |
| | 665 | |
| | 666 | test "autoHash array" { |
| | 667 | 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)))); |
| | 670 | } |
| | 671 | |
| | 672 | test "autoHash struct" { |
| | 673 | const Foo = struct { |
| | 674 | a: u32 = 1, |
| | 675 | b: u32 = 2, |
| | 676 | c: u32 = 3, |
| | 677 | }; |
| | 678 | const f = Foo{}; |
| | 679 | const h = autoHash(f, 0); |
| | 680 | testing.expectEqual(h, autoHash(u32(3), autoHash(u32(2), autoHash(u32(1), 0)))); |
| | 681 | } |
| | 682 | |
| | 683 | test "autoHash union" { |
| | 684 | const Foo = union(enum) { |
| | 685 | A: u32, |
| | 686 | B: f32, |
| | 687 | C: u32, |
| | 688 | }; |
| | 689 | |
| | 690 | const a = Foo{ .A = 18 }; |
| | 691 | var b = Foo{ .B = 12.34 }; |
| | 692 | 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)); |
| | 696 | |
| | 697 | b = Foo{ .A = 18 }; |
| | 698 | testing.expect(autoHash(a, 0) == autoHash(b, 0)); |
| | 699 | } |
| | 700 | |
| | 701 | test "autoHash vector" { |
| | 702 | const a: @Vector(4, u32) = [_]u32{ 1, 2, 3, 4 }; |
| | 703 | const b: @Vector(4, u32) = [_]u32{ 1, 2, 3, 5 }; |
| | 704 | 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)); |
| | 708 | } |
| | 709 | |
| | 710 | test "autoHash error union" { |
| | 711 | const Errors = error{Test}; |
| | 712 | const Foo = struct { |
| | 713 | a: u32 = 1, |
| | 714 | b: u32 = 2, |
| | 715 | c: u32 = 3, |
| | 716 | }; |
| | 717 | const f = Foo{}; |
| | 718 | 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)); |
| | 722 | } |