authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2021-01-06 16:32:23-08:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2021-01-06 16:32:23-08:00
logd7d905696c3e3b0e2b8c691317cb696be940b9a3
tree3bdc251c196100d2bca29b14d38ded9f342ddaf1
parent76870a2265410dc8790b9383cf39610f4b33e3ee
parentd92ea56884c4cdc3a0cff8b6ed1e31f959ee0fa8
signature Signed by PGP key 4AEE18F83AFDEB23

Merge pull request #7622 from tetsuo-cpp/array-hash-map-improvements

std: Support equivalent ArrayList operations in ArrayHashMap

12 files changed, 363 insertions(+), 64 deletions(-)

lib/std/array_hash_map.zig+333-34
...@@ -99,6 +99,16 @@ pub fn ArrayHashMap(...@@ -99,6 +99,16 @@ pub fn ArrayHashMap(
99 };99 };
100 }100 }
101101
102 /// `ArrayHashMap` takes ownership of the passed in array list. The array list must have
103 /// been allocated with `allocator`.
104 /// Deinitialize with `deinit`.
105 pub fn fromOwnedArrayList(allocator: *Allocator, entries: std.ArrayListUnmanaged(Entry)) !Self {
106 return Self{
107 .unmanaged = try Unmanaged.fromOwnedArrayList(allocator, entries),
108 .allocator = allocator,
109 };
110 }
111
102 pub fn deinit(self: *Self) void {112 pub fn deinit(self: *Self) void {
103 self.unmanaged.deinit(self.allocator);113 self.unmanaged.deinit(self.allocator);
104 self.* = undefined;114 self.* = undefined;
...@@ -214,9 +224,19 @@ pub fn ArrayHashMap(...@@ -214,9 +224,19 @@ pub fn ArrayHashMap(
214 }224 }
215225
216 /// If there is an `Entry` with a matching key, it is deleted from226 /// If there is an `Entry` with a matching key, it is deleted from
217 /// the hash map, and then returned from this function.227 /// the hash map, and then returned from this function. The entry is
218 pub fn remove(self: *Self, key: K) ?Entry {228 /// removed from the underlying array by swapping it with the last
219 return self.unmanaged.remove(key);229 /// element.
230 pub fn swapRemove(self: *Self, key: K) ?Entry {
231 return self.unmanaged.swapRemove(key);
232 }
233
234 /// If there is an `Entry` with a matching key, it is deleted from
235 /// the hash map, and then returned from this function. The entry is
236 /// removed from the underlying array by shifting all elements forward
237 /// thereby maintaining the current ordering.
238 pub fn orderedRemove(self: *Self, key: K) ?Entry {
239 return self.unmanaged.orderedRemove(key);
220 }240 }
221241
222 /// Asserts there is an `Entry` with matching key, deletes it from the hash map,242 /// Asserts there is an `Entry` with matching key, deletes it from the hash map,
...@@ -233,6 +253,29 @@ pub fn ArrayHashMap(...@@ -233,6 +253,29 @@ pub fn ArrayHashMap(
233 var other = try self.unmanaged.clone(self.allocator);253 var other = try self.unmanaged.clone(self.allocator);
234 return other.promote(self.allocator);254 return other.promote(self.allocator);
235 }255 }
256
257 /// Rebuilds the key indexes. If the underlying entries has been modified directly, users
258 /// can call `reIndex` to update the indexes to account for these new entries.
259 pub fn reIndex(self: *Self) !void {
260 return self.unmanaged.reIndex(self.allocator);
261 }
262
263 /// Shrinks the underlying `Entry` array to `new_len` elements and discards any associated
264 /// index entries. Keeps capacity the same.
265 pub fn shrinkRetainingCapacity(self: *Self, new_len: usize) void {
266 return self.unmanaged.shrinkRetainingCapacity(new_len);
267 }
268
269 /// Shrinks the underlying `Entry` array to `new_len` elements and discards any associated
270 /// index entries. Reduces allocated capacity.
271 pub fn shrinkAndFree(self: *Self, new_len: usize) void {
272 return self.unmanaged.shrinkAndFree(self.allocator, new_len);
273 }
274
275 /// Removes the last inserted `Entry` in the hash map and returns it.
276 pub fn pop(self: *Self) Entry {
277 return self.unmanaged.pop();
278 }
236 };279 };
237}280}
238281
...@@ -286,6 +329,7 @@ pub fn ArrayHashMapUnmanaged(...@@ -286,6 +329,7 @@ pub fn ArrayHashMapUnmanaged(
286 pub const GetOrPutResult = struct {329 pub const GetOrPutResult = struct {
287 entry: *Entry,330 entry: *Entry,
288 found_existing: bool,331 found_existing: bool,
332 index: usize,
289 };333 };
290334
291 pub const Managed = ArrayHashMap(K, V, hash, eql, store_hash);335 pub const Managed = ArrayHashMap(K, V, hash, eql, store_hash);
...@@ -294,6 +338,12 @@ pub fn ArrayHashMapUnmanaged(...@@ -294,6 +338,12 @@ pub fn ArrayHashMapUnmanaged(
294338
295 const linear_scan_max = 8;339 const linear_scan_max = 8;
296340
341 const RemovalType = enum {
342 swap,
343 ordered,
344 index_only,
345 };
346
297 pub fn promote(self: Self, allocator: *Allocator) Managed {347 pub fn promote(self: Self, allocator: *Allocator) Managed {
298 return .{348 return .{
299 .unmanaged = self,349 .unmanaged = self,
...@@ -301,6 +351,15 @@ pub fn ArrayHashMapUnmanaged(...@@ -301,6 +351,15 @@ pub fn ArrayHashMapUnmanaged(
301 };351 };
302 }352 }
303353
354 /// `ArrayHashMapUnmanaged` takes ownership of the passed in array list. The array list must
355 /// have been allocated with `allocator`.
356 /// Deinitialize with `deinit`.
357 pub fn fromOwnedArrayList(allocator: *Allocator, entries: std.ArrayListUnmanaged(Entry)) !Self {
358 var array_hash_map = Self{ .entries = entries };
359 try array_hash_map.reIndex(allocator);
360 return array_hash_map;
361 }
362
304 pub fn deinit(self: *Self, allocator: *Allocator) void {363 pub fn deinit(self: *Self, allocator: *Allocator) void {
305 self.entries.deinit(allocator);364 self.entries.deinit(allocator);
306 if (self.index_header) |header| {365 if (self.index_header) |header| {
...@@ -323,7 +382,7 @@ pub fn ArrayHashMapUnmanaged(...@@ -323,7 +382,7 @@ pub fn ArrayHashMapUnmanaged(
323 }382 }
324383
325 pub fn clearAndFree(self: *Self, allocator: *Allocator) void {384 pub fn clearAndFree(self: *Self, allocator: *Allocator) void {
326 self.entries.shrink(allocator, 0);385 self.entries.shrinkAndFree(allocator, 0);
327 if (self.index_header) |header| {386 if (self.index_header) |header| {
328 header.free(allocator);387 header.free(allocator);
329 self.index_header = null;388 self.index_header = null;
...@@ -343,9 +402,11 @@ pub fn ArrayHashMapUnmanaged(...@@ -343,9 +402,11 @@ pub fn ArrayHashMapUnmanaged(
343 pub fn getOrPut(self: *Self, allocator: *Allocator, key: K) !GetOrPutResult {402 pub fn getOrPut(self: *Self, allocator: *Allocator, key: K) !GetOrPutResult {
344 self.ensureCapacity(allocator, self.entries.items.len + 1) catch |err| {403 self.ensureCapacity(allocator, self.entries.items.len + 1) catch |err| {
345 // "If key exists this function cannot fail."404 // "If key exists this function cannot fail."
405 const index = self.getIndex(key) orelse return err;
346 return GetOrPutResult{406 return GetOrPutResult{
347 .entry = self.getEntry(key) orelse return err,407 .entry = &self.entries.items[index],
348 .found_existing = true,408 .found_existing = true,
409 .index = index,
349 };410 };
350 };411 };
351 return self.getOrPutAssumeCapacity(key);412 return self.getOrPutAssumeCapacity(key);
...@@ -362,11 +423,12 @@ pub fn ArrayHashMapUnmanaged(...@@ -362,11 +423,12 @@ pub fn ArrayHashMapUnmanaged(
362 const header = self.index_header orelse {423 const header = self.index_header orelse {
363 // Linear scan.424 // Linear scan.
364 const h = if (store_hash) hash(key) else {};425 const h = if (store_hash) hash(key) else {};
365 for (self.entries.items) |*item| {426 for (self.entries.items) |*item, i| {
366 if (item.hash == h and eql(key, item.key)) {427 if (item.hash == h and eql(key, item.key)) {
367 return GetOrPutResult{428 return GetOrPutResult{
368 .entry = item,429 .entry = item,
369 .found_existing = true,430 .found_existing = true,
431 .index = i,
370 };432 };
371 }433 }
372 }434 }
...@@ -379,6 +441,7 @@ pub fn ArrayHashMapUnmanaged(...@@ -379,6 +441,7 @@ pub fn ArrayHashMapUnmanaged(
379 return GetOrPutResult{441 return GetOrPutResult{
380 .entry = new_entry,442 .entry = new_entry,
381 .found_existing = false,443 .found_existing = false,
444 .index = self.entries.items.len - 1,
382 };445 };
383 };446 };
384447
...@@ -524,30 +587,25 @@ pub fn ArrayHashMapUnmanaged(...@@ -524,30 +587,25 @@ pub fn ArrayHashMapUnmanaged(
524 }587 }
525588
526 /// If there is an `Entry` with a matching key, it is deleted from589 /// If there is an `Entry` with a matching key, it is deleted from
527 /// the hash map, and then returned from this function.590 /// the hash map, and then returned from this function. The entry is
528 pub fn remove(self: *Self, key: K) ?Entry {591 /// removed from the underlying array by swapping it with the last
529 const header = self.index_header orelse {592 /// element.
530 // Linear scan.593 pub fn swapRemove(self: *Self, key: K) ?Entry {
531 const h = if (store_hash) hash(key) else {};594 return self.removeInternal(key, .swap);
532 for (self.entries.items) |item, i| {595 }
533 if (item.hash == h and eql(key, item.key)) {596
534 return self.entries.swapRemove(i);597 /// If there is an `Entry` with a matching key, it is deleted from
535 }598 /// the hash map, and then returned from this function. The entry is
536 }599 /// removed from the underlying array by shifting all elements forward
537 return null;600 /// thereby maintaining the current ordering.
538 };601 pub fn orderedRemove(self: *Self, key: K) ?Entry {
539 switch (header.capacityIndexType()) {602 return self.removeInternal(key, .ordered);
540 .u8 => return self.removeInternal(key, header, u8),
541 .u16 => return self.removeInternal(key, header, u16),
542 .u32 => return self.removeInternal(key, header, u32),
543 .usize => return self.removeInternal(key, header, usize),
544 }
545 }603 }
546604
547 /// Asserts there is an `Entry` with matching key, deletes it from the hash map,605 /// Asserts there is an `Entry` with matching key, deletes it from the hash map,
548 /// and discards it.606 /// and discards it.
549 pub fn removeAssertDiscard(self: *Self, key: K) void {607 pub fn removeAssertDiscard(self: *Self, key: K) void {
550 assert(self.remove(key) != null);608 assert(self.swapRemove(key) != null);
551 }609 }
552610
553 pub fn items(self: Self) []Entry {611 pub fn items(self: Self) []Entry {
...@@ -566,9 +624,85 @@ pub fn ArrayHashMapUnmanaged(...@@ -566,9 +624,85 @@ pub fn ArrayHashMapUnmanaged(
566 return other;624 return other;
567 }625 }
568626
569 fn removeInternal(self: *Self, key: K, header: *IndexHeader, comptime I: type) ?Entry {627 /// Rebuilds the key indexes. If the underlying entries has been modified directly, users
628 /// can call `reIndex` to update the indexes to account for these new entries.
629 pub fn reIndex(self: *Self, allocator: *Allocator) !void {
630 if (self.entries.capacity <= linear_scan_max) return;
631 // We're going to rebuild the index header and replace the existing one (if any). The
632 // indexes should sized such that they will be at most 60% full.
633 const needed_len = self.entries.capacity * 5 / 3;
634 const new_indexes_len = math.ceilPowerOfTwo(usize, needed_len) catch unreachable;
635 const new_header = try IndexHeader.alloc(allocator, new_indexes_len);
636 self.insertAllEntriesIntoNewHeader(new_header);
637 if (self.index_header) |header|
638 header.free(allocator);
639 self.index_header = new_header;
640 }
641
642 /// Shrinks the underlying `Entry` array to `new_len` elements and discards any associated
643 /// index entries. Keeps capacity the same.
644 pub fn shrinkRetainingCapacity(self: *Self, new_len: usize) void {
645 // Remove index entries from the new length onwards.
646 // Explicitly choose to ONLY remove index entries and not the underlying array list
647 // entries as we're going to remove them in the subsequent shrink call.
648 var i: usize = new_len;
649 while (i < self.entries.items.len) : (i += 1)
650 _ = self.removeWithHash(self.entries.items[i].key, self.entries.items[i].hash, .index_only);
651 self.entries.shrinkRetainingCapacity(new_len);
652 }
653
654 /// Shrinks the underlying `Entry` array to `new_len` elements and discards any associated
655 /// index entries. Reduces allocated capacity.
656 pub fn shrinkAndFree(self: *Self, allocator: *Allocator, new_len: usize) void {
657 // Remove index entries from the new length onwards.
658 // Explicitly choose to ONLY remove index entries and not the underlying array list
659 // entries as we're going to remove them in the subsequent shrink call.
660 var i: usize = new_len;
661 while (i < self.entries.items.len) : (i += 1)
662 _ = self.removeWithHash(self.entries.items[i].key, self.entries.items[i].hash, .index_only);
663 self.entries.shrinkAndFree(allocator, new_len);
664 }
665
666 /// Removes the last inserted `Entry` in the hash map and returns it.
667 pub fn pop(self: *Self) Entry {
668 const top = self.entries.pop();
669 _ = self.removeWithHash(top.key, top.hash, .index_only);
670 return top;
671 }
672
673 fn removeInternal(self: *Self, key: K, comptime removal_type: RemovalType) ?Entry {
674 const key_hash = if (store_hash) hash(key) else {};
675 return self.removeWithHash(key, key_hash, removal_type);
676 }
677
678 fn removeWithHash(self: *Self, key: K, key_hash: Hash, comptime removal_type: RemovalType) ?Entry {
679 const header = self.index_header orelse {
680 // If we're only removing index entries and we have no index header, there's no need
681 // to continue.
682 if (removal_type == .index_only) return null;
683 // Linear scan.
684 for (self.entries.items) |item, i| {
685 if (item.hash == key_hash and eql(key, item.key)) {
686 switch (removal_type) {
687 .swap => return self.entries.swapRemove(i),
688 .ordered => return self.entries.orderedRemove(i),
689 .index_only => unreachable,
690 }
691 }
692 }
693 return null;
694 };
695 switch (header.capacityIndexType()) {
696 .u8 => return self.removeWithIndex(key, key_hash, header, u8, removal_type),
697 .u16 => return self.removeWithIndex(key, key_hash, header, u16, removal_type),
698 .u32 => return self.removeWithIndex(key, key_hash, header, u32, removal_type),
699 .usize => return self.removeWithIndex(key, key_hash, header, usize, removal_type),
700 }
701 }
702
703 fn removeWithIndex(self: *Self, key: K, key_hash: Hash, header: *IndexHeader, comptime I: type, comptime removal_type: RemovalType) ?Entry {
570 const indexes = header.indexes(I);704 const indexes = header.indexes(I);
571 const h = hash(key);705 const h = if (store_hash) key_hash else hash(key);
572 const start_index = header.constrainIndex(h);706 const start_index = header.constrainIndex(h);
573 var roll_over: usize = 0;707 var roll_over: usize = 0;
574 while (roll_over <= header.max_distance_from_start_index) : (roll_over += 1) {708 while (roll_over <= header.max_distance_from_start_index) : (roll_over += 1) {
...@@ -583,11 +717,26 @@ pub fn ArrayHashMapUnmanaged(...@@ -583,11 +717,26 @@ pub fn ArrayHashMapUnmanaged(
583 if (!hash_match or !eql(key, entry.key))717 if (!hash_match or !eql(key, entry.key))
584 continue;718 continue;
585719
586 const removed_entry = self.entries.swapRemove(index.entry_index);720 var removed_entry: ?Entry = undefined;
587 if (self.entries.items.len > 0 and self.entries.items.len != index.entry_index) {721 switch (removal_type) {
588 // Because of the swap remove, now we need to update the index that was722 .swap => {
589 // pointing to the last entry and is now pointing to this removed item slot.723 removed_entry = self.entries.swapRemove(index.entry_index);
590 self.updateEntryIndex(header, self.entries.items.len, index.entry_index, I, indexes);724 if (self.entries.items.len > 0 and self.entries.items.len != index.entry_index) {
725 // Because of the swap remove, now we need to update the index that was
726 // pointing to the last entry and is now pointing to this removed item slot.
727 self.updateEntryIndex(header, self.entries.items.len, index.entry_index, I, indexes);
728 }
729 },
730 .ordered => {
731 removed_entry = self.entries.orderedRemove(index.entry_index);
732 var i: usize = index.entry_index;
733 while (i < self.entries.items.len) : (i += 1) {
734 // Because of the ordered remove, everything from the entry index onwards has
735 // been shifted forward so we'll need to update the index entries.
736 self.updateEntryIndex(header, i + 1, i, I, indexes);
737 }
738 },
739 .index_only => removed_entry = null,
591 }740 }
592741
593 // Now we have to shift over the following indexes.742 // Now we have to shift over the following indexes.
...@@ -658,6 +807,7 @@ pub fn ArrayHashMapUnmanaged(...@@ -658,6 +807,7 @@ pub fn ArrayHashMapUnmanaged(
658 return .{807 return .{
659 .found_existing = false,808 .found_existing = false,
660 .entry = new_entry,809 .entry = new_entry,
810 .index = self.entries.items.len - 1,
661 };811 };
662 }812 }
663813
...@@ -669,6 +819,7 @@ pub fn ArrayHashMapUnmanaged(...@@ -669,6 +819,7 @@ pub fn ArrayHashMapUnmanaged(
669 return .{819 return .{
670 .found_existing = true,820 .found_existing = true,
671 .entry = entry,821 .entry = entry,
822 .index = index.entry_index,
672 };823 };
673 }824 }
674 if (index.distance_from_start_index < distance_from_start_index) {825 if (index.distance_from_start_index < distance_from_start_index) {
...@@ -710,6 +861,7 @@ pub fn ArrayHashMapUnmanaged(...@@ -710,6 +861,7 @@ pub fn ArrayHashMapUnmanaged(
710 return .{861 return .{
711 .found_existing = false,862 .found_existing = false,
712 .entry = new_entry,863 .entry = new_entry,
864 .index = self.entries.items.len - 1,
713 };865 };
714 }866 }
715 if (next_index.distance_from_start_index < distance_from_start_index) {867 if (next_index.distance_from_start_index < distance_from_start_index) {
...@@ -901,11 +1053,13 @@ test "basic hash map usage" {...@@ -901,11 +1053,13 @@ test "basic hash map usage" {
901 const gop1 = try map.getOrPut(5);1053 const gop1 = try map.getOrPut(5);
902 testing.expect(gop1.found_existing == true);1054 testing.expect(gop1.found_existing == true);
903 testing.expect(gop1.entry.value == 55);1055 testing.expect(gop1.entry.value == 55);
1056 testing.expect(gop1.index == 4);
904 gop1.entry.value = 77;1057 gop1.entry.value = 77;
905 testing.expect(map.getEntry(5).?.value == 77);1058 testing.expect(map.getEntry(5).?.value == 77);
9061059
907 const gop2 = try map.getOrPut(99);1060 const gop2 = try map.getOrPut(99);
908 testing.expect(gop2.found_existing == false);1061 testing.expect(gop2.found_existing == false);
1062 testing.expect(gop2.index == 5);
909 gop2.entry.value = 42;1063 gop2.entry.value = 42;
910 testing.expect(map.getEntry(99).?.value == 42);1064 testing.expect(map.getEntry(99).?.value == 42);
9111065
...@@ -919,13 +1073,32 @@ test "basic hash map usage" {...@@ -919,13 +1073,32 @@ test "basic hash map usage" {
919 testing.expect(map.getEntry(2).?.value == 22);1073 testing.expect(map.getEntry(2).?.value == 22);
920 testing.expect(map.get(2).? == 22);1074 testing.expect(map.get(2).? == 22);
9211075
922 const rmv1 = map.remove(2);1076 const rmv1 = map.swapRemove(2);
923 testing.expect(rmv1.?.key == 2);1077 testing.expect(rmv1.?.key == 2);
924 testing.expect(rmv1.?.value == 22);1078 testing.expect(rmv1.?.value == 22);
925 testing.expect(map.remove(2) == null);1079 testing.expect(map.swapRemove(2) == null);
926 testing.expect(map.getEntry(2) == null);1080 testing.expect(map.getEntry(2) == null);
927 testing.expect(map.get(2) == null);1081 testing.expect(map.get(2) == null);
9281082
1083 // Since we've used `swapRemove` above, the index of this entry should remain unchanged.
1084 testing.expect(map.getIndex(100).? == 1);
1085 const gop5 = try map.getOrPut(5);
1086 testing.expect(gop5.found_existing == true);
1087 testing.expect(gop5.entry.value == 77);
1088 testing.expect(gop5.index == 4);
1089
1090 // Whereas, if we do an `orderedRemove`, it should move the index forward one spot.
1091 const rmv2 = map.orderedRemove(100);
1092 testing.expect(rmv2.?.key == 100);
1093 testing.expect(rmv2.?.value == 41);
1094 testing.expect(map.orderedRemove(100) == null);
1095 testing.expect(map.getEntry(100) == null);
1096 testing.expect(map.get(100) == null);
1097 const gop6 = try map.getOrPut(5);
1098 testing.expect(gop6.found_existing == true);
1099 testing.expect(gop6.entry.value == 77);
1100 testing.expect(gop6.index == 3);
1101
929 map.removeAssertDiscard(3);1102 map.removeAssertDiscard(3);
930}1103}
9311104
...@@ -1019,6 +1192,132 @@ test "clone" {...@@ -1019,6 +1192,132 @@ test "clone" {
1019 }1192 }
1020}1193}
10211194
1195test "shrink" {
1196 var map = AutoArrayHashMap(i32, i32).init(std.testing.allocator);
1197 defer map.deinit();
1198
1199 // This test is more interesting if we insert enough entries to allocate the index header.
1200 const num_entries = 20;
1201 var i: i32 = 0;
1202 while (i < num_entries) : (i += 1)
1203 testing.expect((try map.fetchPut(i, i * 10)) == null);
1204
1205 testing.expect(map.unmanaged.index_header != null);
1206 testing.expect(map.count() == num_entries);
1207
1208 // Test `shrinkRetainingCapacity`.
1209 map.shrinkRetainingCapacity(17);
1210 testing.expect(map.count() == 17);
1211 testing.expect(map.capacity() == 20);
1212 i = 0;
1213 while (i < num_entries) : (i += 1) {
1214 const gop = try map.getOrPut(i);
1215 if (i < 17) {
1216 testing.expect(gop.found_existing == true);
1217 testing.expect(gop.entry.value == i * 10);
1218 } else
1219 testing.expect(gop.found_existing == false);
1220 }
1221
1222 // Test `shrinkAndFree`.
1223 map.shrinkAndFree(15);
1224 testing.expect(map.count() == 15);
1225 testing.expect(map.capacity() == 15);
1226 i = 0;
1227 while (i < num_entries) : (i += 1) {
1228 const gop = try map.getOrPut(i);
1229 if (i < 15) {
1230 testing.expect(gop.found_existing == true);
1231 testing.expect(gop.entry.value == i * 10);
1232 } else
1233 testing.expect(gop.found_existing == false);
1234 }
1235}
1236
1237test "pop" {
1238 var map = AutoArrayHashMap(i32, i32).init(std.testing.allocator);
1239 defer map.deinit();
1240
1241 testing.expect((try map.fetchPut(1, 11)) == null);
1242 testing.expect((try map.fetchPut(2, 22)) == null);
1243 testing.expect((try map.fetchPut(3, 33)) == null);
1244 testing.expect((try map.fetchPut(4, 44)) == null);
1245
1246 const pop1 = map.pop();
1247 testing.expect(pop1.key == 4 and pop1.value == 44);
1248 const pop2 = map.pop();
1249 testing.expect(pop2.key == 3 and pop2.value == 33);
1250 const pop3 = map.pop();
1251 testing.expect(pop3.key == 2 and pop3.value == 22);
1252 const pop4 = map.pop();
1253 testing.expect(pop4.key == 1 and pop4.value == 11);
1254}
1255
1256test "reIndex" {
1257 var map = AutoArrayHashMap(i32, i32).init(std.testing.allocator);
1258 defer map.deinit();
1259
1260 // Populate via the API.
1261 const num_indexed_entries = 20;
1262 var i: i32 = 0;
1263 while (i < num_indexed_entries) : (i += 1)
1264 testing.expect((try map.fetchPut(i, i * 10)) == null);
1265
1266 // Make sure we allocated an index header.
1267 testing.expect(map.unmanaged.index_header != null);
1268
1269 // Now write to the underlying array list directly.
1270 const num_unindexed_entries = 20;
1271 const hash = getAutoHashFn(i32);
1272 var al = &map.unmanaged.entries;
1273 while (i < num_indexed_entries + num_unindexed_entries) : (i += 1) {
1274 try al.append(std.testing.allocator, .{
1275 .key = i,
1276 .value = i * 10,
1277 .hash = hash(i),
1278 });
1279 }
1280
1281 // After reindexing, we should see everything.
1282 try map.reIndex();
1283 i = 0;
1284 while (i < num_indexed_entries + num_unindexed_entries) : (i += 1) {
1285 const gop = try map.getOrPut(i);
1286 testing.expect(gop.found_existing == true);
1287 testing.expect(gop.entry.value == i * 10);
1288 testing.expect(gop.index == i);
1289 }
1290}
1291
1292test "fromOwnedArrayList" {
1293 comptime const array_hash_map_type = AutoArrayHashMap(i32, i32);
1294 var al = std.ArrayListUnmanaged(array_hash_map_type.Entry){};
1295 const hash = getAutoHashFn(i32);
1296
1297 // Populate array list.
1298 const num_entries = 20;
1299 var i: i32 = 0;
1300 while (i < num_entries) : (i += 1) {
1301 try al.append(std.testing.allocator, .{
1302 .key = i,
1303 .value = i * 10,
1304 .hash = hash(i),
1305 });
1306 }
1307
1308 // Now instantiate using `fromOwnedArrayList`.
1309 var map = try array_hash_map_type.fromOwnedArrayList(std.testing.allocator, al);
1310 defer map.deinit();
1311
1312 i = 0;
1313 while (i < num_entries) : (i += 1) {
1314 const gop = try map.getOrPut(i);
1315 testing.expect(gop.found_existing == true);
1316 testing.expect(gop.entry.value == i * 10);
1317 testing.expect(gop.index == i);
1318 }
1319}
1320
1022pub fn getHashPtrAddrFn(comptime K: type) (fn (K) u32) {1321pub fn getHashPtrAddrFn(comptime K: type) (fn (K) u32) {
1023 return struct {1322 return struct {
1024 fn hash(key: K) u32 {1323 fn hash(key: K) u32 {
lib/std/array_list.zig+4-4
...@@ -279,7 +279,7 @@ pub fn ArrayListAligned(comptime T: type, comptime alignment: ?u29) type {...@@ -279,7 +279,7 @@ pub fn ArrayListAligned(comptime T: type, comptime alignment: ?u29) type {
279279
280 /// Reduce allocated capacity to `new_len`.280 /// Reduce allocated capacity to `new_len`.
281 /// May invalidate element pointers.281 /// May invalidate element pointers.
282 pub fn shrink(self: *Self, new_len: usize) void {282 pub fn shrinkAndFree(self: *Self, new_len: usize) void {
283 assert(new_len <= self.items.len);283 assert(new_len <= self.items.len);
284284
285 self.items = self.allocator.realloc(self.allocatedSlice(), new_len) catch |e| switch (e) {285 self.items = self.allocator.realloc(self.allocatedSlice(), new_len) catch |e| switch (e) {
...@@ -587,7 +587,7 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?u29) typ...@@ -587,7 +587,7 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?u29) typ
587 }587 }
588588
589 /// Reduce allocated capacity to `new_len`.589 /// Reduce allocated capacity to `new_len`.
590 pub fn shrink(self: *Self, allocator: *Allocator, new_len: usize) void {590 pub fn shrinkAndFree(self: *Self, allocator: *Allocator, new_len: usize) void {
591 assert(new_len <= self.items.len);591 assert(new_len <= self.items.len);
592592
593 self.items = allocator.realloc(self.allocatedSlice(), new_len) catch |e| switch (e) {593 self.items = allocator.realloc(self.allocatedSlice(), new_len) catch |e| switch (e) {
...@@ -1155,7 +1155,7 @@ test "std.ArrayList/ArrayListUnmanaged.shrink still sets length on error.OutOfMe...@@ -1155,7 +1155,7 @@ test "std.ArrayList/ArrayListUnmanaged.shrink still sets length on error.OutOfMe
1155 try list.append(2);1155 try list.append(2);
1156 try list.append(3);1156 try list.append(3);
11571157
1158 list.shrink(1);1158 list.shrinkAndFree(1);
1159 testing.expect(list.items.len == 1);1159 testing.expect(list.items.len == 1);
1160 }1160 }
1161 {1161 {
...@@ -1165,7 +1165,7 @@ test "std.ArrayList/ArrayListUnmanaged.shrink still sets length on error.OutOfMe...@@ -1165,7 +1165,7 @@ test "std.ArrayList/ArrayListUnmanaged.shrink still sets length on error.OutOfMe
1165 try list.append(a, 2);1165 try list.append(a, 2);
1166 try list.append(a, 3);1166 try list.append(a, 3);
11671167
1168 list.shrink(a, 1);1168 list.shrinkAndFree(a, 1);
1169 testing.expect(list.items.len == 1);1169 testing.expect(list.items.len == 1);
1170 }1170 }
1171}1171}
lib/std/fs.zig+1-1
...@@ -2186,7 +2186,7 @@ pub const Walker = struct {...@@ -2186,7 +2186,7 @@ pub const Walker = struct {
2186 var top = &self.stack.items[self.stack.items.len - 1];2186 var top = &self.stack.items[self.stack.items.len - 1];
2187 const dirname_len = top.dirname_len;2187 const dirname_len = top.dirname_len;
2188 if (try top.dir_it.next()) |base| {2188 if (try top.dir_it.next()) |base| {
2189 self.name_buffer.shrink(dirname_len);2189 self.name_buffer.shrinkAndFree(dirname_len);
2190 try self.name_buffer.append(path.sep);2190 try self.name_buffer.append(path.sep);
2191 try self.name_buffer.appendSlice(base.name);2191 try self.name_buffer.appendSlice(base.name);
2192 if (base.kind == .Directory) {2192 if (base.kind == .Directory) {
lib/std/io/reader.zig+3-3
...@@ -76,12 +76,12 @@ pub fn Reader(...@@ -76,12 +76,12 @@ pub fn Reader(
76 start_index += bytes_read;76 start_index += bytes_read;
7777
78 if (start_index - original_len > max_append_size) {78 if (start_index - original_len > max_append_size) {
79 array_list.shrink(original_len + max_append_size);79 array_list.shrinkAndFree(original_len + max_append_size);
80 return error.StreamTooLong;80 return error.StreamTooLong;
81 }81 }
8282
83 if (bytes_read != dest_slice.len) {83 if (bytes_read != dest_slice.len) {
84 array_list.shrink(start_index);84 array_list.shrinkAndFree(start_index);
85 return;85 return;
86 }86 }
8787
...@@ -111,7 +111,7 @@ pub fn Reader(...@@ -111,7 +111,7 @@ pub fn Reader(
111 delimiter: u8,111 delimiter: u8,
112 max_size: usize,112 max_size: usize,
113 ) !void {113 ) !void {
114 array_list.shrink(0);114 array_list.shrinkAndFree(0);
115 while (true) {115 while (true) {
116 var byte: u8 = try self.readByte();116 var byte: u8 = try self.readByte();
117117
lib/std/json.zig+1-1
...@@ -1897,7 +1897,7 @@ pub const Parser = struct {...@@ -1897,7 +1897,7 @@ pub const Parser = struct {
18971897
1898 pub fn reset(p: *Parser) void {1898 pub fn reset(p: *Parser) void {
1899 p.state = .Simple;1899 p.state = .Simple;
1900 p.stack.shrink(0);1900 p.stack.shrinkAndFree(0);
1901 }1901 }
19021902
1903 pub fn parse(p: *Parser, input: []const u8) !ValueTree {1903 pub fn parse(p: *Parser, input: []const u8) !ValueTree {
lib/std/math/big/int.zig+1-1
...@@ -607,7 +607,7 @@ pub const Mutable = struct {...@@ -607,7 +607,7 @@ pub const Mutable = struct {
607 /// it will have the same length as it had when the function was called.607 /// it will have the same length as it had when the function was called.
608 pub fn gcd(rma: *Mutable, x: Const, y: Const, limbs_buffer: *std.ArrayList(Limb)) !void {608 pub fn gcd(rma: *Mutable, x: Const, y: Const, limbs_buffer: *std.ArrayList(Limb)) !void {
609 const prev_len = limbs_buffer.items.len;609 const prev_len = limbs_buffer.items.len;
610 defer limbs_buffer.shrink(prev_len);610 defer limbs_buffer.shrinkAndFree(prev_len);
611 const x_copy = if (rma.limbs.ptr == x.limbs.ptr) blk: {611 const x_copy = if (rma.limbs.ptr == x.limbs.ptr) blk: {
612 const start = limbs_buffer.items.len;612 const start = limbs_buffer.items.len;
613 try limbs_buffer.appendSlice(x.limbs);613 try limbs_buffer.appendSlice(x.limbs);
lib/std/net.zig+2-2
...@@ -1200,13 +1200,13 @@ fn linuxLookupNameFromDnsSearch(...@@ -1200,13 +1200,13 @@ fn linuxLookupNameFromDnsSearch(
12001200
1201 var tok_it = mem.tokenize(search, " \t");1201 var tok_it = mem.tokenize(search, " \t");
1202 while (tok_it.next()) |tok| {1202 while (tok_it.next()) |tok| {
1203 canon.shrink(canon_name.len + 1);1203 canon.shrinkAndFree(canon_name.len + 1);
1204 try canon.appendSlice(tok);1204 try canon.appendSlice(tok);
1205 try linuxLookupNameFromDns(addrs, canon, canon.items, family, rc, port);1205 try linuxLookupNameFromDns(addrs, canon, canon.items, family, rc, port);
1206 if (addrs.items.len != 0) return;1206 if (addrs.items.len != 0) return;
1207 }1207 }
12081208
1209 canon.shrink(canon_name.len);1209 canon.shrinkAndFree(canon_name.len);
1210 return linuxLookupNameFromDns(addrs, canon, name, family, rc, port);1210 return linuxLookupNameFromDns(addrs, canon, name, family, rc, port);
1211}1211}
12121212
src/Compilation.zig+1-1
...@@ -738,7 +738,7 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation {...@@ -738,7 +738,7 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation {
738 }738 }
739 assert(mem.endsWith(u8, buf.items, ","));739 assert(mem.endsWith(u8, buf.items, ","));
740 buf.items[buf.items.len - 1] = 0;740 buf.items[buf.items.len - 1] = 0;
741 buf.shrink(buf.items.len);741 buf.shrinkAndFree(buf.items.len);
742 break :blk buf.items[0 .. buf.items.len - 1 :0].ptr;742 break :blk buf.items[0 .. buf.items.len - 1 :0].ptr;
743 } else null;743 } else null;
744744
src/Module.zig+8-8
...@@ -594,7 +594,7 @@ pub const Scope = struct {...@@ -594,7 +594,7 @@ pub const Scope = struct {
594 }594 }
595595
596 pub fn removeDecl(self: *Container, child: *Decl) void {596 pub fn removeDecl(self: *Container, child: *Decl) void {
597 _ = self.decls.remove(child);597 _ = self.decls.swapRemove(child);
598 }598 }
599599
600 pub fn fullyQualifiedNameHash(self: *Container, name: []const u8) NameHash {600 pub fn fullyQualifiedNameHash(self: *Container, name: []const u8) NameHash {
...@@ -1710,7 +1710,7 @@ pub fn analyzeContainer(self: *Module, container_scope: *Scope.Container) !void...@@ -1710,7 +1710,7 @@ pub fn analyzeContainer(self: *Module, container_scope: *Scope.Container) !void
1710 // Update the AST Node index of the decl, even if its contents are unchanged, it may1710 // Update the AST Node index of the decl, even if its contents are unchanged, it may
1711 // have been re-ordered.1711 // have been re-ordered.
1712 decl.src_index = decl_i;1712 decl.src_index = decl_i;
1713 if (deleted_decls.remove(decl) == null) {1713 if (deleted_decls.swapRemove(decl) == null) {
1714 decl.analysis = .sema_failure;1714 decl.analysis = .sema_failure;
1715 const err_msg = try Compilation.ErrorMsg.create(self.gpa, tree.token_locs[name_tok].start, "redefinition of '{s}'", .{decl.name});1715 const err_msg = try Compilation.ErrorMsg.create(self.gpa, tree.token_locs[name_tok].start, "redefinition of '{s}'", .{decl.name});
1716 errdefer err_msg.destroy(self.gpa);1716 errdefer err_msg.destroy(self.gpa);
...@@ -1752,7 +1752,7 @@ pub fn analyzeContainer(self: *Module, container_scope: *Scope.Container) !void...@@ -1752,7 +1752,7 @@ pub fn analyzeContainer(self: *Module, container_scope: *Scope.Container) !void
1752 // Update the AST Node index of the decl, even if its contents are unchanged, it may1752 // Update the AST Node index of the decl, even if its contents are unchanged, it may
1753 // have been re-ordered.1753 // have been re-ordered.
1754 decl.src_index = decl_i;1754 decl.src_index = decl_i;
1755 if (deleted_decls.remove(decl) == null) {1755 if (deleted_decls.swapRemove(decl) == null) {
1756 decl.analysis = .sema_failure;1756 decl.analysis = .sema_failure;
1757 const err_msg = try Compilation.ErrorMsg.create(self.gpa, name_loc.start, "redefinition of '{s}'", .{decl.name});1757 const err_msg = try Compilation.ErrorMsg.create(self.gpa, name_loc.start, "redefinition of '{s}'", .{decl.name});
1758 errdefer err_msg.destroy(self.gpa);1758 errdefer err_msg.destroy(self.gpa);
...@@ -1882,7 +1882,7 @@ pub fn deleteDecl(self: *Module, decl: *Decl) !void {...@@ -1882,7 +1882,7 @@ pub fn deleteDecl(self: *Module, decl: *Decl) !void {
1882 try self.markOutdatedDecl(dep);1882 try self.markOutdatedDecl(dep);
1883 }1883 }
1884 }1884 }
1885 if (self.failed_decls.remove(decl)) |entry| {1885 if (self.failed_decls.swapRemove(decl)) |entry| {
1886 entry.value.destroy(self.gpa);1886 entry.value.destroy(self.gpa);
1887 }1887 }
1888 if (self.emit_h_failed_decls.remove(decl)) |entry| {1888 if (self.emit_h_failed_decls.remove(decl)) |entry| {
...@@ -1900,7 +1900,7 @@ pub fn deleteDecl(self: *Module, decl: *Decl) !void {...@@ -1900,7 +1900,7 @@ pub fn deleteDecl(self: *Module, decl: *Decl) !void {
1900/// Delete all the Export objects that are caused by this Decl. Re-analysis of1900/// Delete all the Export objects that are caused by this Decl. Re-analysis of
1901/// this Decl will cause them to be re-created (or not).1901/// this Decl will cause them to be re-created (or not).
1902fn deleteDeclExports(self: *Module, decl: *Decl) void {1902fn deleteDeclExports(self: *Module, decl: *Decl) void {
1903 const kv = self.export_owners.remove(decl) orelse return;1903 const kv = self.export_owners.swapRemove(decl) orelse return;
19041904
1905 for (kv.value) |exp| {1905 for (kv.value) |exp| {
1906 if (self.decl_exports.getEntry(exp.exported_decl)) |decl_exports_kv| {1906 if (self.decl_exports.getEntry(exp.exported_decl)) |decl_exports_kv| {
...@@ -1927,10 +1927,10 @@ fn deleteDeclExports(self: *Module, decl: *Decl) void {...@@ -1927,10 +1927,10 @@ fn deleteDeclExports(self: *Module, decl: *Decl) void {
1927 if (self.comp.bin_file.cast(link.File.MachO)) |macho| {1927 if (self.comp.bin_file.cast(link.File.MachO)) |macho| {
1928 macho.deleteExport(exp.link.macho);1928 macho.deleteExport(exp.link.macho);
1929 }1929 }
1930 if (self.failed_exports.remove(exp)) |entry| {1930 if (self.failed_exports.swapRemove(exp)) |entry| {
1931 entry.value.destroy(self.gpa);1931 entry.value.destroy(self.gpa);
1932 }1932 }
1933 _ = self.symbol_exports.remove(exp.options.name);1933 _ = self.symbol_exports.swapRemove(exp.options.name);
1934 self.gpa.free(exp.options.name);1934 self.gpa.free(exp.options.name);
1935 self.gpa.destroy(exp);1935 self.gpa.destroy(exp);
1936 }1936 }
...@@ -1975,7 +1975,7 @@ pub fn analyzeFnBody(self: *Module, decl: *Decl, func: *Fn) !void {...@@ -1975,7 +1975,7 @@ pub fn analyzeFnBody(self: *Module, decl: *Decl, func: *Fn) !void {
1975fn markOutdatedDecl(self: *Module, decl: *Decl) !void {1975fn markOutdatedDecl(self: *Module, decl: *Decl) !void {
1976 log.debug("mark {s} outdated\n", .{decl.name});1976 log.debug("mark {s} outdated\n", .{decl.name});
1977 try self.comp.work_queue.writeItem(.{ .analyze_decl = decl });1977 try self.comp.work_queue.writeItem(.{ .analyze_decl = decl });
1978 if (self.failed_decls.remove(decl)) |entry| {1978 if (self.failed_decls.swapRemove(decl)) |entry| {
1979 entry.value.destroy(self.gpa);1979 entry.value.destroy(self.gpa);
1980 }1980 }
1981 if (self.emit_h_failed_decls.remove(decl)) |entry| {1981 if (self.emit_h_failed_decls.remove(decl)) |entry| {
src/codegen.zig+1-1
...@@ -2123,7 +2123,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {...@@ -2123,7 +2123,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
2123 try parent_branch.inst_table.ensureCapacity(self.gpa, parent_branch.inst_table.items().len +2123 try parent_branch.inst_table.ensureCapacity(self.gpa, parent_branch.inst_table.items().len +
2124 else_branch.inst_table.items().len);2124 else_branch.inst_table.items().len);
2125 for (else_branch.inst_table.items()) |else_entry| {2125 for (else_branch.inst_table.items()) |else_entry| {
2126 const canon_mcv = if (saved_then_branch.inst_table.remove(else_entry.key)) |then_entry| blk: {2126 const canon_mcv = if (saved_then_branch.inst_table.swapRemove(else_entry.key)) |then_entry| blk: {
2127 // The instruction's MCValue is overridden in both branches.2127 // The instruction's MCValue is overridden in both branches.
2128 parent_branch.inst_table.putAssumeCapacity(else_entry.key, then_entry.value);2128 parent_branch.inst_table.putAssumeCapacity(else_entry.key, then_entry.value);
2129 if (else_entry.value == .dead) {2129 if (else_entry.value == .dead) {
src/libc_installation.zig+3-3
...@@ -337,7 +337,7 @@ pub const LibCInstallation = struct {...@@ -337,7 +337,7 @@ pub const LibCInstallation = struct {
337 defer result_buf.deinit();337 defer result_buf.deinit();
338338
339 for (searches) |search| {339 for (searches) |search| {
340 result_buf.shrink(0);340 result_buf.shrinkAndFree(0);
341 try result_buf.outStream().print("{s}\\Include\\{s}\\ucrt", .{ search.path, search.version });341 try result_buf.outStream().print("{s}\\Include\\{s}\\ucrt", .{ search.path, search.version });
342342
343 var dir = fs.cwd().openDir(result_buf.items, .{}) catch |err| switch (err) {343 var dir = fs.cwd().openDir(result_buf.items, .{}) catch |err| switch (err) {
...@@ -383,7 +383,7 @@ pub const LibCInstallation = struct {...@@ -383,7 +383,7 @@ pub const LibCInstallation = struct {
383 };383 };
384384
385 for (searches) |search| {385 for (searches) |search| {
386 result_buf.shrink(0);386 result_buf.shrinkAndFree(0);
387 try result_buf.outStream().print("{s}\\Lib\\{s}\\ucrt\\{s}", .{ search.path, search.version, arch_sub_dir });387 try result_buf.outStream().print("{s}\\Lib\\{s}\\ucrt\\{s}", .{ search.path, search.version, arch_sub_dir });
388388
389 var dir = fs.cwd().openDir(result_buf.items, .{}) catch |err| switch (err) {389 var dir = fs.cwd().openDir(result_buf.items, .{}) catch |err| switch (err) {
...@@ -437,7 +437,7 @@ pub const LibCInstallation = struct {...@@ -437,7 +437,7 @@ pub const LibCInstallation = struct {
437 };437 };
438438
439 for (searches) |search| {439 for (searches) |search| {
440 result_buf.shrink(0);440 result_buf.shrinkAndFree(0);
441 const stream = result_buf.outStream();441 const stream = result_buf.outStream();
442 try stream.print("{s}\\Lib\\{s}\\um\\{s}", .{ search.path, search.version, arch_sub_dir });442 try stream.print("{s}\\Lib\\{s}\\um\\{s}", .{ search.path, search.version, arch_sub_dir });
443443
src/translate_c.zig+5-5
...@@ -2846,7 +2846,7 @@ fn transCase(...@@ -2846,7 +2846,7 @@ fn transCase(
28462846
2847 // take all pending statements2847 // take all pending statements
2848 try switch_scope.pending_block.statements.appendSlice(block_scope.statements.items);2848 try switch_scope.pending_block.statements.appendSlice(block_scope.statements.items);
2849 block_scope.statements.shrink(0);2849 block_scope.statements.shrinkAndFree(0);
28502850
2851 const pending_node = try switch_scope.pending_block.complete(rp.c);2851 const pending_node = try switch_scope.pending_block.complete(rp.c);
2852 switch_scope.pending_block.deinit();2852 switch_scope.pending_block.deinit();
...@@ -2884,7 +2884,7 @@ fn transDefault(...@@ -2884,7 +2884,7 @@ fn transDefault(
28842884
2885 // take all pending statements2885 // take all pending statements
2886 try switch_scope.pending_block.statements.appendSlice(block_scope.statements.items);2886 try switch_scope.pending_block.statements.appendSlice(block_scope.statements.items);
2887 block_scope.statements.shrink(0);2887 block_scope.statements.shrinkAndFree(0);
28882888
2889 const pending_node = try switch_scope.pending_block.complete(rp.c);2889 const pending_node = try switch_scope.pending_block.complete(rp.c);
2890 switch_scope.pending_block.deinit();2890 switch_scope.pending_block.deinit();
...@@ -4773,9 +4773,9 @@ const RestorePoint = struct {...@@ -4773,9 +4773,9 @@ const RestorePoint = struct {
4773 src_buf_index: usize,4773 src_buf_index: usize,
47744774
4775 fn activate(self: RestorePoint) void {4775 fn activate(self: RestorePoint) void {
4776 self.c.token_ids.shrink(self.c.gpa, self.token_index);4776 self.c.token_ids.shrinkAndFree(self.c.gpa, self.token_index);
4777 self.c.token_locs.shrink(self.c.gpa, self.token_index);4777 self.c.token_locs.shrinkAndFree(self.c.gpa, self.token_index);
4778 self.c.source_buffer.shrink(self.src_buf_index);4778 self.c.source_buffer.shrinkAndFree(self.src_buf_index);
4779 }4779 }
4780};4780};
47814781