| ... | ... | @@ -4,6 +4,8 @@ const assert = debug.assert; |
| 4 | 4 | const testing = std.testing; |
| 5 | 5 | const math = std.math; |
| 6 | 6 | const mem = std.mem; |
| 7 | const meta = std.meta; |
| 8 | const wyhash = std.hash.wyhash; |
| 7 | 9 | const Allocator = mem.Allocator; |
| 8 | 10 | const builtin = @import("builtin"); |
| 9 | 11 | |
| ... | ... | @@ -448,15 +450,17 @@ test "iterator hash map" { |
| 448 | 450 | try reset_map.putNoClobber(2, 22); |
| 449 | 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 | 455 | var keys = [_]i32{ |
| 456 | 1, |
| 452 | 457 | 3, |
| 453 | 458 | 2, |
| 454 | | 1, |
| 455 | 459 | }; |
| 456 | 460 | var values = [_]i32{ |
| 461 | 11, |
| 457 | 462 | 33, |
| 458 | 463 | 22, |
| 459 | | 11, |
| 460 | 464 | }; |
| 461 | 465 | |
| 462 | 466 | var it = reset_map.iterator(); |
| ... | ... | @@ -518,8 +522,8 @@ pub fn getTrivialEqlFn(comptime K: type) (fn (K, K) bool) { |
| 518 | 522 | pub fn getAutoHashFn(comptime K: type) (fn (K) u32) { |
| 519 | 523 | return struct { |
| 520 | 524 | fn hash(key: K) u32 { |
| 521 | | comptime var rng = comptime std.rand.DefaultPrng.init(0); |
| 522 | | return autoHash(key, &rng.random, u32); |
| 525 | const h = autoHash(key, 0); |
| 526 | return @truncate(u32, h); |
| 523 | 527 | } |
| 524 | 528 | }.hash; |
| 525 | 529 | } |
| ... | ... | @@ -527,114 +531,192 @@ pub fn getAutoHashFn(comptime K: type) (fn (K) u32) { |
| 527 | 531 | pub fn getAutoEqlFn(comptime K: type) (fn (K, K) bool) { |
| 528 | 532 | return struct { |
| 529 | 533 | fn eql(a: K, b: K) bool { |
| 530 | | return autoEql(a, b); |
| 534 | return meta.eql(a, b); |
| 531 | 535 | } |
| 532 | 536 | }.eql; |
| 533 | 537 | } |
| 534 | 538 | |
| 535 | | // TODO improve these hash functions |
| 536 | | pub fn autoHash(key: var, comptime rng: *std.rand.Random, comptime HashInt: type) HashInt { |
| 537 | | switch (@typeInfo(@typeOf(key))) { |
| 539 | /// Provides generic hashing for any eligible type. |
| 540 | /// 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. |
| 545 | const Key = @typeOf(key); |
| 546 | switch (@typeInfo(Key)) { |
| 538 | 547 | builtin.TypeId.NoReturn, |
| 539 | 548 | builtin.TypeId.Opaque, |
| 540 | 549 | builtin.TypeId.Undefined, |
| 541 | 550 | builtin.TypeId.ArgTuple, |
| 542 | | => @compileError("cannot hash this type"), |
| 543 | | |
| 544 | 551 | builtin.TypeId.Void, |
| 545 | 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 | 553 | builtin.TypeId.BoundFn, |
| 566 | 554 | builtin.TypeId.ComptimeFloat, |
| 567 | 555 | builtin.TypeId.ComptimeInt, |
| 568 | 556 | builtin.TypeId.Type, |
| 569 | 557 | builtin.TypeId.EnumLiteral, |
| 570 | | => return 0, |
| 571 | | |
| 572 | | builtin.TypeId.Pointer => |info| switch (info.size) { |
| 573 | | builtin.TypeInfo.Pointer.Size.One => @compileError("TODO auto hash for single item pointers"), |
| 574 | | builtin.TypeInfo.Pointer.Size.Many => @compileError("TODO auto hash for many item pointers"), |
| 575 | | builtin.TypeInfo.Pointer.Size.C => @compileError("TODO auto hash C pointers"), |
| 576 | | builtin.TypeInfo.Pointer.Size.Slice => { |
| 577 | | const interval = std.math.max(1, key.len / 256); |
| 578 | | var i: usize = 0; |
| 579 | | var h = comptime rng.scalar(HashInt); |
| 580 | | while (i < key.len) : (i += interval) { |
| 581 | | h ^= autoHash(key[i], rng, HashInt); |
| 582 | | } |
| 583 | | return h; |
| 584 | | }, |
| 558 | => @compileError("cannot hash this type"), |
| 559 | |
| 560 | builtin.TypeId.Int => return wyhash(std.mem.asBytes(&key), seed), |
| 561 | |
| 562 | builtin.TypeId.Float => |info| return autoHash(@bitCast(@IntType(false, info.bits), key), seed), |
| 563 | |
| 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), |
| 568 | |
| 569 | builtin.TypeId.Pointer => |info| return switch (info.size) { |
| 570 | builtin.TypeInfo.Pointer.Size.One, |
| 571 | builtin.TypeInfo.Pointer.Size.Many, |
| 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"), |
| 588 | | builtin.TypeId.Array => @compileError("TODO auto hash for arrays"), |
| 589 | | builtin.TypeId.Vector => @compileError("TODO auto hash for vectors"), |
| 590 | | builtin.TypeId.Struct => @compileError("TODO auto hash for structs"), |
| 591 | | builtin.TypeId.Union => @compileError("TODO auto hash for unions"), |
| 592 | | builtin.TypeId.ErrorUnion => @compileError("TODO auto hash for unions"), |
| 593 | | } |
| 594 | | } |
| 578 | builtin.TypeId.Optional => return if (key) |k| autoHash(k, seed) else 0, |
| 579 | |
| 580 | builtin.TypeId.Array => { |
| 581 | // TODO detect via a trait when Key has no padding bits to |
| 582 | // hash it as an array of bytes. |
| 583 | // Otherwise, hash every element. |
| 584 | var s = seed; |
| 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 { |
| 597 | | switch (@typeInfo(@typeOf(a))) { |
| 598 | | builtin.TypeId.NoReturn, |
| 599 | | builtin.TypeId.Opaque, |
| 600 | | builtin.TypeId.Undefined, |
| 601 | | builtin.TypeId.ArgTuple, |
| 602 | | => @compileError("cannot test equality of this type"), |
| 603 | | builtin.TypeId.Void, |
| 604 | | builtin.TypeId.Null, |
| 605 | | => return true, |
| 606 | | builtin.TypeId.Bool, |
| 607 | | builtin.TypeId.Int, |
| 608 | | builtin.TypeId.Float, |
| 609 | | builtin.TypeId.ComptimeFloat, |
| 610 | | builtin.TypeId.ComptimeInt, |
| 611 | | builtin.TypeId.EnumLiteral, |
| 612 | | builtin.TypeId.Promise, |
| 613 | | builtin.TypeId.Enum, |
| 614 | | builtin.TypeId.BoundFn, |
| 615 | | builtin.TypeId.Fn, |
| 616 | | builtin.TypeId.ErrorSet, |
| 617 | | builtin.TypeId.Type, |
| 618 | | => return a == b, |
| 619 | | |
| 620 | | builtin.TypeId.Pointer => |info| switch (info.size) { |
| 621 | | builtin.TypeInfo.Pointer.Size.One => @compileError("TODO auto eql for single item pointers"), |
| 622 | | builtin.TypeInfo.Pointer.Size.Many => @compileError("TODO auto eql for many item pointers"), |
| 623 | | builtin.TypeInfo.Pointer.Size.C => @compileError("TODO auto eql for C pointers"), |
| 624 | | builtin.TypeInfo.Pointer.Size.Slice => { |
| 625 | | if (a.len != b.len) return false; |
| 626 | | for (a) |a_item, i| { |
| 627 | | if (!autoEql(a_item, b[i])) return false; |
| 593 | 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 | if (info.child.bit_count % 8 == 0) { |
| 597 | return wyhash(mem.asBytes(&key), seed); |
| 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); |
| 607 | } |
| 608 | return s; |
| 609 | }, |
| 610 | |
| 611 | builtin.TypeId.Struct => |info| { |
| 612 | // TODO detect via a trait when Key has no padding bits to |
| 613 | // hash it as an array of bytes. |
| 614 | // Otherwise, hash every field. |
| 615 | var s = seed; |
| 616 | inline for (info.fields) |field| { |
| 617 | // We reuse the hash of the previous field as the seed for the |
| 618 | // next one so that they're dependant. |
| 619 | s = autoHash(@field(key, field.name), s); |
| 620 | } |
| 621 | return s; |
| 622 | }, |
| 623 | |
| 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; |
| 630 | | }, |
| 634 | unreachable; |
| 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"), |
| 634 | | builtin.TypeId.Array => @compileError("TODO auto eql for arrays"), |
| 635 | | builtin.TypeId.Struct => @compileError("TODO auto eql for structs"), |
| 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"), |
| 638 | builtin.TypeId.ErrorUnion => { |
| 639 | return autoHash(key catch |err| return autoHash(err, seed), seed); |
| 640 | }, |
| 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 | } |