| ... | @@ -1007,10 +1007,8 @@ pub fn HashMapUnmanaged( | ... | @@ -1007,10 +1007,8 @@ pub fn HashMapUnmanaged( |
| 1007 | metadata = self.metadata.? + idx; | 1007 | metadata = self.metadata.? + idx; |
| 1008 | } | 1008 | } |
| 1009 | | 1009 | |
| 1010 | if (!metadata[0].isTombstone()) { | 1010 | assert(self.available > 0); |
| 1011 | assert(self.available > 0); | 1011 | self.available -= 1; |
| 1012 | self.available -= 1; | | |
| 1013 | } | | |
| 1014 | | 1012 | |
| 1015 | const fingerprint = Metadata.takeFingerprint(hash); | 1013 | const fingerprint = Metadata.takeFingerprint(hash); |
| 1016 | metadata[0].fill(fingerprint); | 1014 | metadata[0].fill(fingerprint); |
| ... | @@ -1112,10 +1110,12 @@ pub fn HashMapUnmanaged( | ... | @@ -1112,10 +1110,12 @@ pub fn HashMapUnmanaged( |
| 1112 | } | 1110 | } |
| 1113 | const mask = self.capacity() - 1; | 1111 | const mask = self.capacity() - 1; |
| 1114 | const fingerprint = Metadata.takeFingerprint(hash); | 1112 | const fingerprint = Metadata.takeFingerprint(hash); |
| | 1113 | // Don't loop indefinitely when there are no empty slots. |
| | 1114 | var limit = self.capacity(); |
| 1115 | var idx = @truncate(usize, hash & mask); | 1115 | var idx = @truncate(usize, hash & mask); |
| 1116 | | 1116 | |
| 1117 | var metadata = self.metadata.? + idx; | 1117 | var metadata = self.metadata.? + idx; |
| 1118 | while (metadata[0].isUsed() or metadata[0].isTombstone()) { | 1118 | while ((metadata[0].isUsed() or metadata[0].isTombstone()) and limit != 0) { |
| 1119 | if (metadata[0].isUsed() and metadata[0].fingerprint == fingerprint) { | 1119 | if (metadata[0].isUsed() and metadata[0].fingerprint == fingerprint) { |
| 1120 | const test_key = &self.keys()[idx]; | 1120 | const test_key = &self.keys()[idx]; |
| 1121 | // If you get a compile error on this line, it means that your generic eql | 1121 | // If you get a compile error on this line, it means that your generic eql |
| ... | @@ -1131,6 +1131,7 @@ pub fn HashMapUnmanaged( | ... | @@ -1131,6 +1131,7 @@ pub fn HashMapUnmanaged( |
| 1131 | } | 1131 | } |
| 1132 | } | 1132 | } |
| 1133 | | 1133 | |
| | 1134 | limit -= 1; |
| 1134 | idx = (idx + 1) & mask; | 1135 | idx = (idx + 1) & mask; |
| 1135 | metadata = self.metadata.? + idx; | 1136 | metadata = self.metadata.? + idx; |
| 1136 | } | 1137 | } |
| ... | @@ -1288,11 +1289,12 @@ pub fn HashMapUnmanaged( | ... | @@ -1288,11 +1289,12 @@ pub fn HashMapUnmanaged( |
| 1288 | } | 1289 | } |
| 1289 | const mask = self.capacity() - 1; | 1290 | const mask = self.capacity() - 1; |
| 1290 | const fingerprint = Metadata.takeFingerprint(hash); | 1291 | const fingerprint = Metadata.takeFingerprint(hash); |
| | 1292 | var limit = self.capacity(); |
| 1291 | var idx = @truncate(usize, hash & mask); | 1293 | var idx = @truncate(usize, hash & mask); |
| 1292 | | 1294 | |
| 1293 | var first_tombstone_idx: usize = self.capacity(); // invalid index | 1295 | var first_tombstone_idx: usize = self.capacity(); // invalid index |
| 1294 | var metadata = self.metadata.? + idx; | 1296 | var metadata = self.metadata.? + idx; |
| 1295 | while (metadata[0].isUsed() or metadata[0].isTombstone()) { | 1297 | while ((metadata[0].isUsed() or metadata[0].isTombstone()) and limit != 0) { |
| 1296 | if (metadata[0].isUsed() and metadata[0].fingerprint == fingerprint) { | 1298 | if (metadata[0].isUsed() and metadata[0].fingerprint == fingerprint) { |
| 1297 | const test_key = &self.keys()[idx]; | 1299 | const test_key = &self.keys()[idx]; |
| 1298 | // If you get a compile error on this line, it means that your generic eql | 1300 | // If you get a compile error on this line, it means that your generic eql |
| ... | @@ -1314,6 +1316,7 @@ pub fn HashMapUnmanaged( | ... | @@ -1314,6 +1316,7 @@ pub fn HashMapUnmanaged( |
| 1314 | first_tombstone_idx = idx; | 1316 | first_tombstone_idx = idx; |
| 1315 | } | 1317 | } |
| 1316 | | 1318 | |
| | 1319 | limit -= 1; |
| 1317 | idx = (idx + 1) & mask; | 1320 | idx = (idx + 1) & mask; |
| 1318 | metadata = self.metadata.? + idx; | 1321 | metadata = self.metadata.? + idx; |
| 1319 | } | 1322 | } |
| ... | @@ -1322,10 +1325,9 @@ pub fn HashMapUnmanaged( | ... | @@ -1322,10 +1325,9 @@ pub fn HashMapUnmanaged( |
| 1322 | // Cheap try to lower probing lengths after deletions. Recycle a tombstone. | 1325 | // Cheap try to lower probing lengths after deletions. Recycle a tombstone. |
| 1323 | idx = first_tombstone_idx; | 1326 | idx = first_tombstone_idx; |
| 1324 | metadata = self.metadata.? + idx; | 1327 | metadata = self.metadata.? + idx; |
| 1325 | } else { | | |
| 1326 | // We're using a slot previously free. | | |
| 1327 | self.available -= 1; | | |
| 1328 | } | 1328 | } |
| | 1329 | // We're using a slot previously free or a tombstone. |
| | 1330 | self.available -= 1; |
| 1329 | | 1331 | |
| 1330 | metadata[0].fill(fingerprint); | 1332 | metadata[0].fill(fingerprint); |
| 1331 | const new_key = &self.keys()[idx]; | 1333 | const new_key = &self.keys()[idx]; |
| ... | @@ -1385,6 +1387,7 @@ pub fn HashMapUnmanaged( | ... | @@ -1385,6 +1387,7 @@ pub fn HashMapUnmanaged( |
| 1385 | self.keys()[idx] = undefined; | 1387 | self.keys()[idx] = undefined; |
| 1386 | self.values()[idx] = undefined; | 1388 | self.values()[idx] = undefined; |
| 1387 | self.size -= 1; | 1389 | self.size -= 1; |
| | 1390 | self.available += 1; |
| 1388 | return true; | 1391 | return true; |
| 1389 | } | 1392 | } |
| 1390 | | 1393 | |
| ... | @@ -1395,7 +1398,7 @@ pub fn HashMapUnmanaged( | ... | @@ -1395,7 +1398,7 @@ pub fn HashMapUnmanaged( |
| 1395 | @memset(@ptrCast([*]u8, self.metadata.?), 0, @sizeOf(Metadata) * self.capacity()); | 1398 | @memset(@ptrCast([*]u8, self.metadata.?), 0, @sizeOf(Metadata) * self.capacity()); |
| 1396 | } | 1399 | } |
| 1397 | | 1400 | |
| 1398 | // This counts the number of occupied slots, used + tombstones, which is | 1401 | // This counts the number of occupied slots (not counting tombstones), which is |
| 1399 | // what has to stay under the max_load_percentage of capacity. | 1402 | // what has to stay under the max_load_percentage of capacity. |
| 1400 | fn load(self: *const Self) Size { | 1403 | fn load(self: *const Self) Size { |
| 1401 | const max_load = (self.capacity() * max_load_percentage) / 100; | 1404 | const max_load = (self.capacity() * max_load_percentage) / 100; |
| ... | @@ -1587,7 +1590,6 @@ test "std.hash_map ensureUnusedCapacity with tombstones" { | ... | @@ -1587,7 +1590,6 @@ test "std.hash_map ensureUnusedCapacity with tombstones" { |
| 1587 | while (i < 100) : (i += 1) { | 1590 | while (i < 100) : (i += 1) { |
| 1588 | try map.ensureUnusedCapacity(1); | 1591 | try map.ensureUnusedCapacity(1); |
| 1589 | map.putAssumeCapacity(i, i); | 1592 | map.putAssumeCapacity(i, i); |
| 1590 | // Remove to create tombstones that still count as load in the hashmap. | | |
| 1591 | _ = map.remove(i); | 1593 | _ = map.remove(i); |
| 1592 | } | 1594 | } |
| 1593 | } | 1595 | } |
| ... | @@ -1894,6 +1896,38 @@ test "std.hash_map putAssumeCapacity" { | ... | @@ -1894,6 +1896,38 @@ test "std.hash_map putAssumeCapacity" { |
| 1894 | try expectEqual(sum, 20); | 1896 | try expectEqual(sum, 20); |
| 1895 | } | 1897 | } |
| 1896 | | 1898 | |
| | 1899 | test "std.hash_map repeat putAssumeCapacity/remove" { |
| | 1900 | var map = AutoHashMap(u32, u32).init(std.testing.allocator); |
| | 1901 | defer map.deinit(); |
| | 1902 | |
| | 1903 | try map.ensureTotalCapacity(20); |
| | 1904 | const limit = map.unmanaged.available; |
| | 1905 | |
| | 1906 | var i: u32 = 0; |
| | 1907 | while (i < limit) : (i += 1) { |
| | 1908 | map.putAssumeCapacityNoClobber(i, i); |
| | 1909 | } |
| | 1910 | |
| | 1911 | // Repeatedly delete/insert an entry without resizing the map. |
| | 1912 | // Put to different keys so entries don't land in the just-freed slot. |
| | 1913 | i = 0; |
| | 1914 | while (i < 10 * limit) : (i += 1) { |
| | 1915 | try testing.expect(map.remove(i)); |
| | 1916 | if (i % 2 == 0) { |
| | 1917 | map.putAssumeCapacityNoClobber(limit + i, i); |
| | 1918 | } else { |
| | 1919 | map.putAssumeCapacity(limit + i, i); |
| | 1920 | } |
| | 1921 | } |
| | 1922 | |
| | 1923 | i = 9 * limit; |
| | 1924 | while (i < 10 * limit) : (i += 1) { |
| | 1925 | try expectEqual(map.get(limit + i), i); |
| | 1926 | } |
| | 1927 | try expectEqual(map.unmanaged.available, 0); |
| | 1928 | try expectEqual(map.unmanaged.count(), limit); |
| | 1929 | } |
| | 1930 | |
| 1897 | test "std.hash_map getOrPut" { | 1931 | test "std.hash_map getOrPut" { |
| 1898 | var map = AutoHashMap(u32, u32).init(std.testing.allocator); | 1932 | var map = AutoHashMap(u32, u32).init(std.testing.allocator); |
| 1899 | defer map.deinit(); | 1933 | defer map.deinit(); |