| ... | ... | @@ -137,6 +137,23 @@ pub fn ArrayHashMap( |
| 137 | 137 | self.* = undefined; |
| 138 | 138 | } |
| 139 | 139 | |
| 140 | /// Puts the hash map into a state where any method call that would |
| 141 | /// cause an existing key or value pointer to become invalidated will |
| 142 | /// instead trigger an assertion. |
| 143 | /// |
| 144 | /// An additional call to `lockPointers` in such state also triggers an |
| 145 | /// assertion. |
| 146 | /// |
| 147 | /// `unlockPointers` returns the hash map to the previous state. |
| 148 | pub fn lockPointers(self: *Self) void { |
| 149 | self.unmanaged.lockPointers(); |
| 150 | } |
| 151 | |
| 152 | /// Undoes a call to `lockPointers`. |
| 153 | pub fn unlockPointers(self: *Self) void { |
| 154 | self.unmanaged.unlockPointers(); |
| 155 | } |
| 156 | |
| 140 | 157 | /// Clears the map but retains the backing allocation for future use. |
| 141 | 158 | pub fn clearRetainingCapacity(self: *Self) void { |
| 142 | 159 | return self.unmanaged.clearRetainingCapacity(); |
| ... | ... | @@ -403,6 +420,7 @@ pub fn ArrayHashMap( |
| 403 | 420 | /// Set the map to an empty state, making deinitialization a no-op, and |
| 404 | 421 | /// returning a copy of the original. |
| 405 | 422 | pub fn move(self: *Self) Self { |
| 423 | self.pointer_stability.assertUnlocked(); |
| 406 | 424 | const result = self.*; |
| 407 | 425 | self.unmanaged = .{}; |
| 408 | 426 | return result; |
| ... | ... | @@ -495,6 +513,9 @@ pub fn ArrayHashMapUnmanaged( |
| 495 | 513 | /// by how many total indexes there are. |
| 496 | 514 | index_header: ?*IndexHeader = null, |
| 497 | 515 | |
| 516 | /// Used to detect memory safety violations. |
| 517 | pointer_stability: std.debug.SafetyLock = .{}, |
| 518 | |
| 498 | 519 | comptime { |
| 499 | 520 | std.hash_map.verifyContext(Context, K, K, u32, true); |
| 500 | 521 | } |
| ... | ... | @@ -589,6 +610,7 @@ pub fn ArrayHashMapUnmanaged( |
| 589 | 610 | /// Note that this does not free keys or values. You must take care of that |
| 590 | 611 | /// before calling this function, if it is needed. |
| 591 | 612 | pub fn deinit(self: *Self, allocator: Allocator) void { |
| 613 | self.pointer_stability.assertUnlocked(); |
| 592 | 614 | self.entries.deinit(allocator); |
| 593 | 615 | if (self.index_header) |header| { |
| 594 | 616 | header.free(allocator); |
| ... | ... | @@ -596,8 +618,28 @@ pub fn ArrayHashMapUnmanaged( |
| 596 | 618 | self.* = undefined; |
| 597 | 619 | } |
| 598 | 620 | |
| 621 | /// Puts the hash map into a state where any method call that would |
| 622 | /// cause an existing key or value pointer to become invalidated will |
| 623 | /// instead trigger an assertion. |
| 624 | /// |
| 625 | /// An additional call to `lockPointers` in such state also triggers an |
| 626 | /// assertion. |
| 627 | /// |
| 628 | /// `unlockPointers` returns the hash map to the previous state. |
| 629 | pub fn lockPointers(self: *Self) void { |
| 630 | self.pointer_stability.lock(); |
| 631 | } |
| 632 | |
| 633 | /// Undoes a call to `lockPointers`. |
| 634 | pub fn unlockPointers(self: *Self) void { |
| 635 | self.pointer_stability.unlock(); |
| 636 | } |
| 637 | |
| 599 | 638 | /// Clears the map but retains the backing allocation for future use. |
| 600 | 639 | pub fn clearRetainingCapacity(self: *Self) void { |
| 640 | self.pointer_stability.lock(); |
| 641 | defer self.pointer_stability.unlock(); |
| 642 | |
| 601 | 643 | self.entries.len = 0; |
| 602 | 644 | if (self.index_header) |header| { |
| 603 | 645 | switch (header.capacityIndexType()) { |
| ... | ... | @@ -610,6 +652,9 @@ pub fn ArrayHashMapUnmanaged( |
| 610 | 652 | |
| 611 | 653 | /// Clears the map and releases the backing allocation |
| 612 | 654 | pub fn clearAndFree(self: *Self, allocator: Allocator) void { |
| 655 | self.pointer_stability.lock(); |
| 656 | defer self.pointer_stability.unlock(); |
| 657 | |
| 613 | 658 | self.entries.shrinkAndFree(allocator, 0); |
| 614 | 659 | if (self.index_header) |header| { |
| 615 | 660 | header.free(allocator); |
| ... | ... | @@ -795,6 +840,9 @@ pub fn ArrayHashMapUnmanaged( |
| 795 | 840 | return self.ensureTotalCapacityContext(allocator, new_capacity, undefined); |
| 796 | 841 | } |
| 797 | 842 | pub fn ensureTotalCapacityContext(self: *Self, allocator: Allocator, new_capacity: usize, ctx: Context) !void { |
| 843 | self.pointer_stability.lock(); |
| 844 | defer self.pointer_stability.unlock(); |
| 845 | |
| 798 | 846 | if (new_capacity <= linear_scan_max) { |
| 799 | 847 | try self.entries.ensureTotalCapacity(allocator, new_capacity); |
| 800 | 848 | return; |
| ... | ... | @@ -1079,6 +1127,9 @@ pub fn ArrayHashMapUnmanaged( |
| 1079 | 1127 | return self.fetchSwapRemoveContextAdapted(key, ctx, undefined); |
| 1080 | 1128 | } |
| 1081 | 1129 | pub fn fetchSwapRemoveContextAdapted(self: *Self, key: anytype, key_ctx: anytype, ctx: Context) ?KV { |
| 1130 | self.pointer_stability.lock(); |
| 1131 | defer self.pointer_stability.unlock(); |
| 1132 | |
| 1082 | 1133 | return self.fetchRemoveByKey(key, key_ctx, if (store_hash) {} else ctx, .swap); |
| 1083 | 1134 | } |
| 1084 | 1135 | |
| ... | ... | @@ -1100,6 +1151,9 @@ pub fn ArrayHashMapUnmanaged( |
| 1100 | 1151 | return self.fetchOrderedRemoveContextAdapted(key, ctx, undefined); |
| 1101 | 1152 | } |
| 1102 | 1153 | pub fn fetchOrderedRemoveContextAdapted(self: *Self, key: anytype, key_ctx: anytype, ctx: Context) ?KV { |
| 1154 | self.pointer_stability.lock(); |
| 1155 | defer self.pointer_stability.unlock(); |
| 1156 | |
| 1103 | 1157 | return self.fetchRemoveByKey(key, key_ctx, if (store_hash) {} else ctx, .ordered); |
| 1104 | 1158 | } |
| 1105 | 1159 | |
| ... | ... | @@ -1121,6 +1175,9 @@ pub fn ArrayHashMapUnmanaged( |
| 1121 | 1175 | return self.swapRemoveContextAdapted(key, ctx, undefined); |
| 1122 | 1176 | } |
| 1123 | 1177 | pub fn swapRemoveContextAdapted(self: *Self, key: anytype, key_ctx: anytype, ctx: Context) bool { |
| 1178 | self.pointer_stability.lock(); |
| 1179 | defer self.pointer_stability.unlock(); |
| 1180 | |
| 1124 | 1181 | return self.removeByKey(key, key_ctx, if (store_hash) {} else ctx, .swap); |
| 1125 | 1182 | } |
| 1126 | 1183 | |
| ... | ... | @@ -1142,6 +1199,9 @@ pub fn ArrayHashMapUnmanaged( |
| 1142 | 1199 | return self.orderedRemoveContextAdapted(key, ctx, undefined); |
| 1143 | 1200 | } |
| 1144 | 1201 | pub fn orderedRemoveContextAdapted(self: *Self, key: anytype, key_ctx: anytype, ctx: Context) bool { |
| 1202 | self.pointer_stability.lock(); |
| 1203 | defer self.pointer_stability.unlock(); |
| 1204 | |
| 1145 | 1205 | return self.removeByKey(key, key_ctx, if (store_hash) {} else ctx, .ordered); |
| 1146 | 1206 | } |
| 1147 | 1207 | |
| ... | ... | @@ -1154,6 +1214,9 @@ pub fn ArrayHashMapUnmanaged( |
| 1154 | 1214 | return self.swapRemoveAtContext(index, undefined); |
| 1155 | 1215 | } |
| 1156 | 1216 | pub fn swapRemoveAtContext(self: *Self, index: usize, ctx: Context) void { |
| 1217 | self.pointer_stability.lock(); |
| 1218 | defer self.pointer_stability.unlock(); |
| 1219 | |
| 1157 | 1220 | self.removeByIndex(index, if (store_hash) {} else ctx, .swap); |
| 1158 | 1221 | } |
| 1159 | 1222 | |
| ... | ... | @@ -1167,6 +1230,9 @@ pub fn ArrayHashMapUnmanaged( |
| 1167 | 1230 | return self.orderedRemoveAtContext(index, undefined); |
| 1168 | 1231 | } |
| 1169 | 1232 | pub fn orderedRemoveAtContext(self: *Self, index: usize, ctx: Context) void { |
| 1233 | self.pointer_stability.lock(); |
| 1234 | defer self.pointer_stability.unlock(); |
| 1235 | |
| 1170 | 1236 | self.removeByIndex(index, if (store_hash) {} else ctx, .ordered); |
| 1171 | 1237 | } |
| 1172 | 1238 | |
| ... | ... | @@ -1196,6 +1262,7 @@ pub fn ArrayHashMapUnmanaged( |
| 1196 | 1262 | /// Set the map to an empty state, making deinitialization a no-op, and |
| 1197 | 1263 | /// returning a copy of the original. |
| 1198 | 1264 | pub fn move(self: *Self) Self { |
| 1265 | self.pointer_stability.assertUnlocked(); |
| 1199 | 1266 | const result = self.*; |
| 1200 | 1267 | self.* = .{}; |
| 1201 | 1268 | return result; |
| ... | ... | @@ -1271,6 +1338,9 @@ pub fn ArrayHashMapUnmanaged( |
| 1271 | 1338 | sort_ctx: anytype, |
| 1272 | 1339 | ctx: Context, |
| 1273 | 1340 | ) void { |
| 1341 | self.pointer_stability.lock(); |
| 1342 | defer self.pointer_stability.unlock(); |
| 1343 | |
| 1274 | 1344 | switch (mode) { |
| 1275 | 1345 | .stable => self.entries.sort(sort_ctx), |
| 1276 | 1346 | .unstable => self.entries.sortUnstable(sort_ctx), |
| ... | ... | @@ -1288,6 +1358,9 @@ pub fn ArrayHashMapUnmanaged( |
| 1288 | 1358 | return self.shrinkRetainingCapacityContext(new_len, undefined); |
| 1289 | 1359 | } |
| 1290 | 1360 | pub fn shrinkRetainingCapacityContext(self: *Self, new_len: usize, ctx: Context) void { |
| 1361 | self.pointer_stability.lock(); |
| 1362 | defer self.pointer_stability.unlock(); |
| 1363 | |
| 1291 | 1364 | // Remove index entries from the new length onwards. |
| 1292 | 1365 | // Explicitly choose to ONLY remove index entries and not the underlying array list |
| 1293 | 1366 | // entries as we're going to remove them in the subsequent shrink call. |
| ... | ... | @@ -1307,6 +1380,9 @@ pub fn ArrayHashMapUnmanaged( |
| 1307 | 1380 | return self.shrinkAndFreeContext(allocator, new_len, undefined); |
| 1308 | 1381 | } |
| 1309 | 1382 | pub fn shrinkAndFreeContext(self: *Self, allocator: Allocator, new_len: usize, ctx: Context) void { |
| 1383 | self.pointer_stability.lock(); |
| 1384 | defer self.pointer_stability.unlock(); |
| 1385 | |
| 1310 | 1386 | // Remove index entries from the new length onwards. |
| 1311 | 1387 | // Explicitly choose to ONLY remove index entries and not the underlying array list |
| 1312 | 1388 | // entries as we're going to remove them in the subsequent shrink call. |
| ... | ... | @@ -1325,6 +1401,9 @@ pub fn ArrayHashMapUnmanaged( |
| 1325 | 1401 | return self.popContext(undefined); |
| 1326 | 1402 | } |
| 1327 | 1403 | pub fn popContext(self: *Self, ctx: Context) KV { |
| 1404 | self.pointer_stability.lock(); |
| 1405 | defer self.pointer_stability.unlock(); |
| 1406 | |
| 1328 | 1407 | const item = self.entries.get(self.entries.len - 1); |
| 1329 | 1408 | if (self.index_header) |header| |
| 1330 | 1409 | self.removeFromIndexByIndex(self.entries.len - 1, if (store_hash) {} else ctx, header); |
| ... | ... | @@ -1346,9 +1425,13 @@ pub fn ArrayHashMapUnmanaged( |
| 1346 | 1425 | return if (self.entries.len == 0) null else self.popContext(ctx); |
| 1347 | 1426 | } |
| 1348 | 1427 | |
| 1349 | | // ------------------ No pub fns below this point ------------------ |
| 1350 | | |
| 1351 | | fn fetchRemoveByKey(self: *Self, key: anytype, key_ctx: anytype, ctx: ByIndexContext, comptime removal_type: RemovalType) ?KV { |
| 1428 | fn fetchRemoveByKey( |
| 1429 | self: *Self, |
| 1430 | key: anytype, |
| 1431 | key_ctx: anytype, |
| 1432 | ctx: ByIndexContext, |
| 1433 | comptime removal_type: RemovalType, |
| 1434 | ) ?KV { |
| 1352 | 1435 | const header = self.index_header orelse { |
| 1353 | 1436 | // Linear scan. |
| 1354 | 1437 | const key_hash = if (store_hash) key_ctx.hash(key) else {}; |
| ... | ... | @@ -1377,7 +1460,15 @@ pub fn ArrayHashMapUnmanaged( |
| 1377 | 1460 | .u32 => self.fetchRemoveByKeyGeneric(key, key_ctx, ctx, header, u32, removal_type), |
| 1378 | 1461 | }; |
| 1379 | 1462 | } |
| 1380 | | fn fetchRemoveByKeyGeneric(self: *Self, key: anytype, key_ctx: anytype, ctx: ByIndexContext, header: *IndexHeader, comptime I: type, comptime removal_type: RemovalType) ?KV { |
| 1463 | fn fetchRemoveByKeyGeneric( |
| 1464 | self: *Self, |
| 1465 | key: anytype, |
| 1466 | key_ctx: anytype, |
| 1467 | ctx: ByIndexContext, |
| 1468 | header: *IndexHeader, |
| 1469 | comptime I: type, |
| 1470 | comptime removal_type: RemovalType, |
| 1471 | ) ?KV { |
| 1381 | 1472 | const indexes = header.indexes(I); |
| 1382 | 1473 | const entry_index = self.removeFromIndexByKey(key, key_ctx, header, I, indexes) orelse return null; |
| 1383 | 1474 | const slice = self.entries.slice(); |
| ... | ... | @@ -1389,7 +1480,13 @@ pub fn ArrayHashMapUnmanaged( |
| 1389 | 1480 | return removed_entry; |
| 1390 | 1481 | } |
| 1391 | 1482 | |
| 1392 | | fn removeByKey(self: *Self, key: anytype, key_ctx: anytype, ctx: ByIndexContext, comptime removal_type: RemovalType) bool { |
| 1483 | fn removeByKey( |
| 1484 | self: *Self, |
| 1485 | key: anytype, |
| 1486 | key_ctx: anytype, |
| 1487 | ctx: ByIndexContext, |
| 1488 | comptime removal_type: RemovalType, |
| 1489 | ) bool { |
| 1393 | 1490 | const header = self.index_header orelse { |
| 1394 | 1491 | // Linear scan. |
| 1395 | 1492 | const key_hash = if (store_hash) key_ctx.hash(key) else {}; |