authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-11-28 14:07:30-05:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2024-11-28 14:07:30-05:00
log182cdf74bfb010936e167405cdb54c9f5a94e1a6
tree56b42f6971e317d7e9e5bb80ddf8f5f8a223d74d
parent8594f179f9d70fbc3bf39111c4e1f147d4a6dc3c
parente374483d67592907522141546450e89e4d2f5d1e
signaturebadge-check Signed by PGP key B5690EEEBB952194

Merge pull request #22087 from ziglang/std.ArrayHashMap

std.ArrayHashMap: add `reinit` method and other housekeeping, including the move towards "unmanaged" containers

1 files changed, 88 insertions(+), 74 deletions(-)

lib/std/array_hash_map.zig+88-74
......@@ -53,6 +53,14 @@ pub fn hashString(s: []const u8) u32 {
5353 return @as(u32, @truncate(std.hash.Wyhash.hash(0, s)));
5454}
5555
56/// Deprecated in favor of `ArrayHashMapWithAllocator` (no code changes needed)
57/// or `ArrayHashMapUnmanaged` (will need to update callsites to pass an
58/// allocator). After Zig 0.14.0 is released, `ArrayHashMapWithAllocator` will
59/// be removed and `ArrayHashMapUnmanaged` will be a deprecated alias. After
60/// Zig 0.15.0 is released, the deprecated alias `ArrayHashMapUnmanaged` will
61/// be removed.
62pub const ArrayHashMap = ArrayHashMapWithAllocator;
63
5664/// A hash table of keys and values, each stored sequentially.
5765///
5866/// Insertion order is preserved. In general, this data structure supports the same
......@@ -67,7 +75,7 @@ pub fn hashString(s: []const u8) u32 {
6775///
6876/// See `ArrayHashMapUnmanaged` for a variant of this data structure that accepts an
6977/// `Allocator` as a parameter when needed rather than storing it.
70pub fn ArrayHashMap(
78pub fn ArrayHashMapWithAllocator(
7179 comptime K: type,
7280 comptime V: type,
7381 /// A namespace that provides these two functions:
......@@ -604,42 +612,48 @@ pub fn ArrayHashMapUnmanaged(
604612 ordered,
605613 };
606614
615 const Oom = Allocator.Error;
616
607617 /// Convert from an unmanaged map to a managed map. After calling this,
608618 /// the promoted map should no longer be used.
609 pub fn promote(self: Self, allocator: Allocator) Managed {
619 pub fn promote(self: Self, gpa: Allocator) Managed {
610620 if (@sizeOf(Context) != 0)
611621 @compileError("Cannot infer context " ++ @typeName(Context) ++ ", call promoteContext instead.");
612 return self.promoteContext(allocator, undefined);
622 return self.promoteContext(gpa, undefined);
613623 }
614 pub fn promoteContext(self: Self, allocator: Allocator, ctx: Context) Managed {
624 pub fn promoteContext(self: Self, gpa: Allocator, ctx: Context) Managed {
615625 return .{
616626 .unmanaged = self,
617 .allocator = allocator,
627 .allocator = gpa,
618628 .ctx = ctx,
619629 };
620630 }
621631
622 pub fn init(allocator: Allocator, key_list: []const K, value_list: []const V) !Self {
632 pub fn init(gpa: Allocator, key_list: []const K, value_list: []const V) Oom!Self {
623633 var self: Self = .{};
624 try self.entries.resize(allocator, key_list.len);
625 errdefer self.entries.deinit(allocator);
634 errdefer self.deinit(gpa);
635 try self.reinit(gpa, key_list, value_list);
636 return self;
637 }
638
639 pub fn reinit(self: *Self, gpa: Allocator, key_list: []const K, value_list: []const V) Oom!void {
640 try self.entries.resize(gpa, key_list.len);
626641 @memcpy(self.keys(), key_list);
627642 if (@sizeOf(V) != 0) {
628643 assert(key_list.len == value_list.len);
629644 @memcpy(self.values(), value_list);
630645 }
631 try self.reIndex(allocator);
632 return self;
646 try self.reIndex(gpa);
633647 }
634648
635649 /// Frees the backing allocation and leaves the map in an undefined state.
636650 /// Note that this does not free keys or values. You must take care of that
637651 /// before calling this function, if it is needed.
638 pub fn deinit(self: *Self, allocator: Allocator) void {
652 pub fn deinit(self: *Self, gpa: Allocator) void {
639653 self.pointer_stability.assertUnlocked();
640 self.entries.deinit(allocator);
654 self.entries.deinit(gpa);
641655 if (self.index_header) |header| {
642 header.free(allocator);
656 header.free(gpa);
643657 }
644658 self.* = undefined;
645659 }
......@@ -677,13 +691,13 @@ pub fn ArrayHashMapUnmanaged(
677691 }
678692
679693 /// Clears the map and releases the backing allocation
680 pub fn clearAndFree(self: *Self, allocator: Allocator) void {
694 pub fn clearAndFree(self: *Self, gpa: Allocator) void {
681695 self.pointer_stability.lock();
682696 defer self.pointer_stability.unlock();
683697
684 self.entries.shrinkAndFree(allocator, 0);
698 self.entries.shrinkAndFree(gpa, 0);
685699 if (self.index_header) |header| {
686 header.free(allocator);
700 header.free(gpa);
687701 self.index_header = null;
688702 }
689703 }
......@@ -746,25 +760,25 @@ pub fn ArrayHashMapUnmanaged(
746760 /// Otherwise, puts a new item with undefined value, and
747761 /// the `Entry` pointer points to it. Caller should then initialize
748762 /// the value (but not the key).
749 pub fn getOrPut(self: *Self, allocator: Allocator, key: K) !GetOrPutResult {
763 pub fn getOrPut(self: *Self, gpa: Allocator, key: K) Oom!GetOrPutResult {
750764 if (@sizeOf(Context) != 0)
751765 @compileError("Cannot infer context " ++ @typeName(Context) ++ ", call getOrPutContext instead.");
752 return self.getOrPutContext(allocator, key, undefined);
766 return self.getOrPutContext(gpa, key, undefined);
753767 }
754 pub fn getOrPutContext(self: *Self, allocator: Allocator, key: K, ctx: Context) !GetOrPutResult {
755 const gop = try self.getOrPutContextAdapted(allocator, key, ctx, ctx);
768 pub fn getOrPutContext(self: *Self, gpa: Allocator, key: K, ctx: Context) Oom!GetOrPutResult {
769 const gop = try self.getOrPutContextAdapted(gpa, key, ctx, ctx);
756770 if (!gop.found_existing) {
757771 gop.key_ptr.* = key;
758772 }
759773 return gop;
760774 }
761 pub fn getOrPutAdapted(self: *Self, allocator: Allocator, key: anytype, key_ctx: anytype) !GetOrPutResult {
775 pub fn getOrPutAdapted(self: *Self, gpa: Allocator, key: anytype, key_ctx: anytype) Oom!GetOrPutResult {
762776 if (@sizeOf(Context) != 0)
763777 @compileError("Cannot infer context " ++ @typeName(Context) ++ ", call getOrPutContextAdapted instead.");
764 return self.getOrPutContextAdapted(allocator, key, key_ctx, undefined);
778 return self.getOrPutContextAdapted(gpa, key, key_ctx, undefined);
765779 }
766 pub fn getOrPutContextAdapted(self: *Self, allocator: Allocator, key: anytype, key_ctx: anytype, ctx: Context) !GetOrPutResult {
767 self.ensureTotalCapacityContext(allocator, self.entries.len + 1, ctx) catch |err| {
780 pub fn getOrPutContextAdapted(self: *Self, gpa: Allocator, key: anytype, key_ctx: anytype, ctx: Context) Oom!GetOrPutResult {
781 self.ensureTotalCapacityContext(gpa, self.entries.len + 1, ctx) catch |err| {
768782 // "If key exists this function cannot fail."
769783 const index = self.getIndexAdapted(key, key_ctx) orelse return err;
770784 const slice = self.entries.slice();
......@@ -844,13 +858,13 @@ pub fn ArrayHashMapUnmanaged(
844858 }
845859 }
846860
847 pub fn getOrPutValue(self: *Self, allocator: Allocator, key: K, value: V) !GetOrPutResult {
861 pub fn getOrPutValue(self: *Self, gpa: Allocator, key: K, value: V) Oom!GetOrPutResult {
848862 if (@sizeOf(Context) != 0)
849863 @compileError("Cannot infer context " ++ @typeName(Context) ++ ", call getOrPutValueContext instead.");
850 return self.getOrPutValueContext(allocator, key, value, undefined);
864 return self.getOrPutValueContext(gpa, key, value, undefined);
851865 }
852 pub fn getOrPutValueContext(self: *Self, allocator: Allocator, key: K, value: V, ctx: Context) !GetOrPutResult {
853 const res = try self.getOrPutContextAdapted(allocator, key, ctx, ctx);
866 pub fn getOrPutValueContext(self: *Self, gpa: Allocator, key: K, value: V, ctx: Context) Oom!GetOrPutResult {
867 const res = try self.getOrPutContextAdapted(gpa, key, ctx, ctx);
854868 if (!res.found_existing) {
855869 res.key_ptr.* = key;
856870 res.value_ptr.* = value;
......@@ -860,32 +874,32 @@ pub fn ArrayHashMapUnmanaged(
860874
861875 /// Increases capacity, guaranteeing that insertions up until the
862876 /// `expected_count` will not cause an allocation, and therefore cannot fail.
863 pub fn ensureTotalCapacity(self: *Self, allocator: Allocator, new_capacity: usize) !void {
877 pub fn ensureTotalCapacity(self: *Self, gpa: Allocator, new_capacity: usize) Oom!void {
864878 if (@sizeOf(ByIndexContext) != 0)
865879 @compileError("Cannot infer context " ++ @typeName(Context) ++ ", call ensureTotalCapacityContext instead.");
866 return self.ensureTotalCapacityContext(allocator, new_capacity, undefined);
880 return self.ensureTotalCapacityContext(gpa, new_capacity, undefined);
867881 }
868 pub fn ensureTotalCapacityContext(self: *Self, allocator: Allocator, new_capacity: usize, ctx: Context) !void {
882 pub fn ensureTotalCapacityContext(self: *Self, gpa: Allocator, new_capacity: usize, ctx: Context) Oom!void {
869883 self.pointer_stability.lock();
870884 defer self.pointer_stability.unlock();
871885
872886 if (new_capacity <= linear_scan_max) {
873 try self.entries.ensureTotalCapacity(allocator, new_capacity);
887 try self.entries.ensureTotalCapacity(gpa, new_capacity);
874888 return;
875889 }
876890
877891 if (self.index_header) |header| {
878892 if (new_capacity <= header.capacity()) {
879 try self.entries.ensureTotalCapacity(allocator, new_capacity);
893 try self.entries.ensureTotalCapacity(gpa, new_capacity);
880894 return;
881895 }
882896 }
883897
884 try self.entries.ensureTotalCapacity(allocator, new_capacity);
898 try self.entries.ensureTotalCapacity(gpa, new_capacity);
885899 const new_bit_index = try IndexHeader.findBitIndex(new_capacity);
886 const new_header = try IndexHeader.alloc(allocator, new_bit_index);
900 const new_header = try IndexHeader.alloc(gpa, new_bit_index);
887901
888 if (self.index_header) |old_header| old_header.free(allocator);
902 if (self.index_header) |old_header| old_header.free(gpa);
889903 self.insertAllEntriesIntoNewHeader(if (store_hash) {} else ctx, new_header);
890904 self.index_header = new_header;
891905 }
......@@ -895,20 +909,20 @@ pub fn ArrayHashMapUnmanaged(
895909 /// therefore cannot fail.
896910 pub fn ensureUnusedCapacity(
897911 self: *Self,
898 allocator: Allocator,
912 gpa: Allocator,
899913 additional_capacity: usize,
900 ) !void {
914 ) Oom!void {
901915 if (@sizeOf(Context) != 0)
902916 @compileError("Cannot infer context " ++ @typeName(Context) ++ ", call ensureTotalCapacityContext instead.");
903 return self.ensureUnusedCapacityContext(allocator, additional_capacity, undefined);
917 return self.ensureUnusedCapacityContext(gpa, additional_capacity, undefined);
904918 }
905919 pub fn ensureUnusedCapacityContext(
906920 self: *Self,
907 allocator: Allocator,
921 gpa: Allocator,
908922 additional_capacity: usize,
909923 ctx: Context,
910 ) !void {
911 return self.ensureTotalCapacityContext(allocator, self.count() + additional_capacity, ctx);
924 ) Oom!void {
925 return self.ensureTotalCapacityContext(gpa, self.count() + additional_capacity, ctx);
912926 }
913927
914928 /// Returns the number of total elements which may be present before it is
......@@ -922,25 +936,25 @@ pub fn ArrayHashMapUnmanaged(
922936
923937 /// Clobbers any existing data. To detect if a put would clobber
924938 /// existing data, see `getOrPut`.
925 pub fn put(self: *Self, allocator: Allocator, key: K, value: V) !void {
939 pub fn put(self: *Self, gpa: Allocator, key: K, value: V) Oom!void {
926940 if (@sizeOf(Context) != 0)
927941 @compileError("Cannot infer context " ++ @typeName(Context) ++ ", call putContext instead.");
928 return self.putContext(allocator, key, value, undefined);
942 return self.putContext(gpa, key, value, undefined);
929943 }
930 pub fn putContext(self: *Self, allocator: Allocator, key: K, value: V, ctx: Context) !void {
931 const result = try self.getOrPutContext(allocator, key, ctx);
944 pub fn putContext(self: *Self, gpa: Allocator, key: K, value: V, ctx: Context) Oom!void {
945 const result = try self.getOrPutContext(gpa, key, ctx);
932946 result.value_ptr.* = value;
933947 }
934948
935949 /// Inserts a key-value pair into the hash map, asserting that no previous
936950 /// entry with the same key is already present
937 pub fn putNoClobber(self: *Self, allocator: Allocator, key: K, value: V) !void {
951 pub fn putNoClobber(self: *Self, gpa: Allocator, key: K, value: V) Oom!void {
938952 if (@sizeOf(Context) != 0)
939953 @compileError("Cannot infer context " ++ @typeName(Context) ++ ", call putNoClobberContext instead.");
940 return self.putNoClobberContext(allocator, key, value, undefined);
954 return self.putNoClobberContext(gpa, key, value, undefined);
941955 }
942 pub fn putNoClobberContext(self: *Self, allocator: Allocator, key: K, value: V, ctx: Context) !void {
943 const result = try self.getOrPutContext(allocator, key, ctx);
956 pub fn putNoClobberContext(self: *Self, gpa: Allocator, key: K, value: V, ctx: Context) Oom!void {
957 const result = try self.getOrPutContext(gpa, key, ctx);
944958 assert(!result.found_existing);
945959 result.value_ptr.* = value;
946960 }
......@@ -973,13 +987,13 @@ pub fn ArrayHashMapUnmanaged(
973987 }
974988
975989 /// Inserts a new `Entry` into the hash map, returning the previous one, if any.
976 pub fn fetchPut(self: *Self, allocator: Allocator, key: K, value: V) !?KV {
990 pub fn fetchPut(self: *Self, gpa: Allocator, key: K, value: V) Oom!?KV {
977991 if (@sizeOf(Context) != 0)
978992 @compileError("Cannot infer context " ++ @typeName(Context) ++ ", call fetchPutContext instead.");
979 return self.fetchPutContext(allocator, key, value, undefined);
993 return self.fetchPutContext(gpa, key, value, undefined);
980994 }
981 pub fn fetchPutContext(self: *Self, allocator: Allocator, key: K, value: V, ctx: Context) !?KV {
982 const gop = try self.getOrPutContext(allocator, key, ctx);
995 pub fn fetchPutContext(self: *Self, gpa: Allocator, key: K, value: V, ctx: Context) Oom!?KV {
996 const gop = try self.getOrPutContext(gpa, key, ctx);
983997 var result: ?KV = null;
984998 if (gop.found_existing) {
985999 result = KV{
......@@ -1265,20 +1279,20 @@ pub fn ArrayHashMapUnmanaged(
12651279 /// Create a copy of the hash map which can be modified separately.
12661280 /// The copy uses the same context as this instance, but is allocated
12671281 /// with the provided allocator.
1268 pub fn clone(self: Self, allocator: Allocator) !Self {
1282 pub fn clone(self: Self, gpa: Allocator) Oom!Self {
12691283 if (@sizeOf(ByIndexContext) != 0)
12701284 @compileError("Cannot infer context " ++ @typeName(Context) ++ ", call cloneContext instead.");
1271 return self.cloneContext(allocator, undefined);
1285 return self.cloneContext(gpa, undefined);
12721286 }
1273 pub fn cloneContext(self: Self, allocator: Allocator, ctx: Context) !Self {
1287 pub fn cloneContext(self: Self, gpa: Allocator, ctx: Context) Oom!Self {
12741288 var other: Self = .{};
1275 other.entries = try self.entries.clone(allocator);
1276 errdefer other.entries.deinit(allocator);
1289 other.entries = try self.entries.clone(gpa);
1290 errdefer other.entries.deinit(gpa);
12771291
12781292 if (self.index_header) |header| {
12791293 // TODO: I'm pretty sure this could be memcpy'd instead of
12801294 // doing all this work.
1281 const new_header = try IndexHeader.alloc(allocator, header.bit_index);
1295 const new_header = try IndexHeader.alloc(gpa, header.bit_index);
12821296 other.insertAllEntriesIntoNewHeader(if (store_hash) {} else ctx, new_header);
12831297 other.index_header = new_header;
12841298 }
......@@ -1304,13 +1318,13 @@ pub fn ArrayHashMapUnmanaged(
13041318 /// directly without going through the methods of this map.
13051319 ///
13061320 /// The time complexity of this operation is O(n).
1307 pub fn reIndex(self: *Self, allocator: Allocator) !void {
1321 pub fn reIndex(self: *Self, gpa: Allocator) Oom!void {
13081322 if (@sizeOf(ByIndexContext) != 0)
13091323 @compileError("Cannot infer context " ++ @typeName(Context) ++ ", call reIndexContext instead.");
1310 return self.reIndexContext(allocator, undefined);
1324 return self.reIndexContext(gpa, undefined);
13111325 }
13121326
1313 pub fn reIndexContext(self: *Self, allocator: Allocator, ctx: Context) !void {
1327 pub fn reIndexContext(self: *Self, gpa: Allocator, ctx: Context) Oom!void {
13141328 // Recompute all hashes.
13151329 if (store_hash) {
13161330 for (self.keys(), self.entries.items(.hash)) |key, *hash| {
......@@ -1323,8 +1337,8 @@ pub fn ArrayHashMapUnmanaged(
13231337 // We're going to rebuild the index header and replace the existing one (if any). The
13241338 // indexes should sized such that they will be at most 60% full.
13251339 const bit_index = try IndexHeader.findBitIndex(self.entries.capacity);
1326 const new_header = try IndexHeader.alloc(allocator, bit_index);
1327 if (self.index_header) |header| header.free(allocator);
1340 const new_header = try IndexHeader.alloc(gpa, bit_index);
1341 if (self.index_header) |header| header.free(gpa);
13281342 self.insertAllEntriesIntoNewHeader(if (store_hash) {} else ctx, new_header);
13291343 self.index_header = new_header;
13301344 }
......@@ -1416,10 +1430,10 @@ pub fn ArrayHashMapUnmanaged(
14161430 /// performing hash and equality checks. It is a bug to call this
14171431 /// function if the discarded entries require deinitialization. For
14181432 /// that use case, `shrinkRetainingCapacity` can be used instead.
1419 pub fn shrinkAndFree(self: *Self, allocator: Allocator, new_len: usize) void {
1433 pub fn shrinkAndFree(self: *Self, gpa: Allocator, new_len: usize) void {
14201434 if (@sizeOf(ByIndexContext) != 0)
14211435 @compileError("Cannot infer context " ++ @typeName(Context) ++ ", call shrinkAndFreeContext instead.");
1422 return self.shrinkAndFreeContext(allocator, new_len, undefined);
1436 return self.shrinkAndFreeContext(gpa, new_len, undefined);
14231437 }
14241438
14251439 /// Shrinks the underlying `Entry` array to `new_len` elements and
......@@ -1430,7 +1444,7 @@ pub fn ArrayHashMapUnmanaged(
14301444 /// function if the discarded entries require deinitialization. For
14311445 /// that use case, `shrinkRetainingCapacityContext` can be used
14321446 /// instead.
1433 pub fn shrinkAndFreeContext(self: *Self, allocator: Allocator, new_len: usize, ctx: Context) void {
1447 pub fn shrinkAndFreeContext(self: *Self, gpa: Allocator, new_len: usize, ctx: Context) void {
14341448 self.pointer_stability.lock();
14351449 defer self.pointer_stability.unlock();
14361450
......@@ -1442,7 +1456,7 @@ pub fn ArrayHashMapUnmanaged(
14421456 while (i < self.entries.len) : (i += 1)
14431457 self.removeFromIndexByIndex(i, if (store_hash) {} else ctx, header);
14441458 }
1445 self.entries.shrinkAndFree(allocator, new_len);
1459 self.entries.shrinkAndFree(gpa, new_len);
14461460 }
14471461
14481462 /// Removes the last inserted `Entry` in the hash map and returns it.
......@@ -2086,7 +2100,7 @@ const IndexHeader = struct {
20862100 return @as(u32, @intCast(self.length() - 1));
20872101 }
20882102
2089 fn findBitIndex(desired_capacity: usize) !u8 {
2103 fn findBitIndex(desired_capacity: usize) Allocator.Error!u8 {
20902104 if (desired_capacity > max_capacity) return error.OutOfMemory;
20912105 var new_bit_index = @as(u8, @intCast(std.math.log2_int_ceil(usize, desired_capacity)));
20922106 if (desired_capacity > index_capacities[new_bit_index]) new_bit_index += 1;
......@@ -2097,11 +2111,11 @@ const IndexHeader = struct {
20972111
20982112 /// Allocates an index header, and fills the entryIndexes array with empty.
20992113 /// The distance array contents are undefined.
2100 fn alloc(allocator: Allocator, new_bit_index: u8) !*IndexHeader {
2114 fn alloc(gpa: Allocator, new_bit_index: u8) Allocator.Error!*IndexHeader {
21012115 const len = @as(usize, 1) << @as(math.Log2Int(usize), @intCast(new_bit_index));
21022116 const index_size = hash_map.capacityIndexSize(new_bit_index);
21032117 const nbytes = @sizeOf(IndexHeader) + index_size * len;
2104 const bytes = try allocator.alignedAlloc(u8, @alignOf(IndexHeader), nbytes);
2118 const bytes = try gpa.alignedAlloc(u8, @alignOf(IndexHeader), nbytes);
21052119 @memset(bytes[@sizeOf(IndexHeader)..], 0xff);
21062120 const result: *IndexHeader = @alignCast(@ptrCast(bytes.ptr));
21072121 result.* = .{
......@@ -2111,11 +2125,11 @@ const IndexHeader = struct {
21112125 }
21122126
21132127 /// Releases the memory for a header and its associated arrays.
2114 fn free(header: *IndexHeader, allocator: Allocator) void {
2128 fn free(header: *IndexHeader, gpa: Allocator) void {
21152129 const index_size = hash_map.capacityIndexSize(header.bit_index);
21162130 const ptr: [*]align(@alignOf(IndexHeader)) u8 = @ptrCast(header);
21172131 const slice = ptr[0 .. @sizeOf(IndexHeader) + header.length() * index_size];
2118 allocator.free(slice);
2132 gpa.free(slice);
21192133 }
21202134
21212135 /// Puts an IndexHeader into the state that it would be in after being freshly allocated.