authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-03-16 15:45:10-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2024-03-16 18:45:10-04:00
log242ab81112c05fa815523551a6f612c5a12c52b2
tree32b581f036137436932883ffdfab24aec64576db
parent1b8d1b18c7ec1c8002046d8a7e131bf21ccf92ca
signaturebadge-check Signed by PGP key B5690EEEBB952194

std: introduce pointer stability locks to hash maps (#17719)

This adds std.debug.SafetyLock and uses it in std.HashMapUnmanaged by adding lockPointers() and unlockPointers(). This provides a way to detect when an illegal modification has happened and panic rather than invoke undefined behavior.

3 files changed, 203 insertions(+), 31 deletions(-)

lib/std/array_hash_map.zig+102-5
...@@ -137,6 +137,23 @@ pub fn ArrayHashMap(...@@ -137,6 +137,23 @@ pub fn ArrayHashMap(
137 self.* = undefined;137 self.* = undefined;
138 }138 }
139139
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 /// Clears the map but retains the backing allocation for future use.157 /// Clears the map but retains the backing allocation for future use.
141 pub fn clearRetainingCapacity(self: *Self) void {158 pub fn clearRetainingCapacity(self: *Self) void {
142 return self.unmanaged.clearRetainingCapacity();159 return self.unmanaged.clearRetainingCapacity();
...@@ -403,6 +420,7 @@ pub fn ArrayHashMap(...@@ -403,6 +420,7 @@ pub fn ArrayHashMap(
403 /// Set the map to an empty state, making deinitialization a no-op, and420 /// Set the map to an empty state, making deinitialization a no-op, and
404 /// returning a copy of the original.421 /// returning a copy of the original.
405 pub fn move(self: *Self) Self {422 pub fn move(self: *Self) Self {
423 self.pointer_stability.assertUnlocked();
406 const result = self.*;424 const result = self.*;
407 self.unmanaged = .{};425 self.unmanaged = .{};
408 return result;426 return result;
...@@ -495,6 +513,9 @@ pub fn ArrayHashMapUnmanaged(...@@ -495,6 +513,9 @@ pub fn ArrayHashMapUnmanaged(
495 /// by how many total indexes there are.513 /// by how many total indexes there are.
496 index_header: ?*IndexHeader = null,514 index_header: ?*IndexHeader = null,
497515
516 /// Used to detect memory safety violations.
517 pointer_stability: std.debug.SafetyLock = .{},
518
498 comptime {519 comptime {
499 std.hash_map.verifyContext(Context, K, K, u32, true);520 std.hash_map.verifyContext(Context, K, K, u32, true);
500 }521 }
...@@ -589,6 +610,7 @@ pub fn ArrayHashMapUnmanaged(...@@ -589,6 +610,7 @@ pub fn ArrayHashMapUnmanaged(
589 /// Note that this does not free keys or values. You must take care of that610 /// Note that this does not free keys or values. You must take care of that
590 /// before calling this function, if it is needed.611 /// before calling this function, if it is needed.
591 pub fn deinit(self: *Self, allocator: Allocator) void {612 pub fn deinit(self: *Self, allocator: Allocator) void {
613 self.pointer_stability.assertUnlocked();
592 self.entries.deinit(allocator);614 self.entries.deinit(allocator);
593 if (self.index_header) |header| {615 if (self.index_header) |header| {
594 header.free(allocator);616 header.free(allocator);
...@@ -596,8 +618,28 @@ pub fn ArrayHashMapUnmanaged(...@@ -596,8 +618,28 @@ pub fn ArrayHashMapUnmanaged(
596 self.* = undefined;618 self.* = undefined;
597 }619 }
598620
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 /// Clears the map but retains the backing allocation for future use.638 /// Clears the map but retains the backing allocation for future use.
600 pub fn clearRetainingCapacity(self: *Self) void {639 pub fn clearRetainingCapacity(self: *Self) void {
640 self.pointer_stability.lock();
641 defer self.pointer_stability.unlock();
642
601 self.entries.len = 0;643 self.entries.len = 0;
602 if (self.index_header) |header| {644 if (self.index_header) |header| {
603 switch (header.capacityIndexType()) {645 switch (header.capacityIndexType()) {
...@@ -610,6 +652,9 @@ pub fn ArrayHashMapUnmanaged(...@@ -610,6 +652,9 @@ pub fn ArrayHashMapUnmanaged(
610652
611 /// Clears the map and releases the backing allocation653 /// Clears the map and releases the backing allocation
612 pub fn clearAndFree(self: *Self, allocator: Allocator) void {654 pub fn clearAndFree(self: *Self, allocator: Allocator) void {
655 self.pointer_stability.lock();
656 defer self.pointer_stability.unlock();
657
613 self.entries.shrinkAndFree(allocator, 0);658 self.entries.shrinkAndFree(allocator, 0);
614 if (self.index_header) |header| {659 if (self.index_header) |header| {
615 header.free(allocator);660 header.free(allocator);
...@@ -795,6 +840,9 @@ pub fn ArrayHashMapUnmanaged(...@@ -795,6 +840,9 @@ pub fn ArrayHashMapUnmanaged(
795 return self.ensureTotalCapacityContext(allocator, new_capacity, undefined);840 return self.ensureTotalCapacityContext(allocator, new_capacity, undefined);
796 }841 }
797 pub fn ensureTotalCapacityContext(self: *Self, allocator: Allocator, new_capacity: usize, ctx: Context) !void {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 if (new_capacity <= linear_scan_max) {846 if (new_capacity <= linear_scan_max) {
799 try self.entries.ensureTotalCapacity(allocator, new_capacity);847 try self.entries.ensureTotalCapacity(allocator, new_capacity);
800 return;848 return;
...@@ -1079,6 +1127,9 @@ pub fn ArrayHashMapUnmanaged(...@@ -1079,6 +1127,9 @@ pub fn ArrayHashMapUnmanaged(
1079 return self.fetchSwapRemoveContextAdapted(key, ctx, undefined);1127 return self.fetchSwapRemoveContextAdapted(key, ctx, undefined);
1080 }1128 }
1081 pub fn fetchSwapRemoveContextAdapted(self: *Self, key: anytype, key_ctx: anytype, ctx: Context) ?KV {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 return self.fetchRemoveByKey(key, key_ctx, if (store_hash) {} else ctx, .swap);1133 return self.fetchRemoveByKey(key, key_ctx, if (store_hash) {} else ctx, .swap);
1083 }1134 }
10841135
...@@ -1100,6 +1151,9 @@ pub fn ArrayHashMapUnmanaged(...@@ -1100,6 +1151,9 @@ pub fn ArrayHashMapUnmanaged(
1100 return self.fetchOrderedRemoveContextAdapted(key, ctx, undefined);1151 return self.fetchOrderedRemoveContextAdapted(key, ctx, undefined);
1101 }1152 }
1102 pub fn fetchOrderedRemoveContextAdapted(self: *Self, key: anytype, key_ctx: anytype, ctx: Context) ?KV {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 return self.fetchRemoveByKey(key, key_ctx, if (store_hash) {} else ctx, .ordered);1157 return self.fetchRemoveByKey(key, key_ctx, if (store_hash) {} else ctx, .ordered);
1104 }1158 }
11051159
...@@ -1121,6 +1175,9 @@ pub fn ArrayHashMapUnmanaged(...@@ -1121,6 +1175,9 @@ pub fn ArrayHashMapUnmanaged(
1121 return self.swapRemoveContextAdapted(key, ctx, undefined);1175 return self.swapRemoveContextAdapted(key, ctx, undefined);
1122 }1176 }
1123 pub fn swapRemoveContextAdapted(self: *Self, key: anytype, key_ctx: anytype, ctx: Context) bool {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 return self.removeByKey(key, key_ctx, if (store_hash) {} else ctx, .swap);1181 return self.removeByKey(key, key_ctx, if (store_hash) {} else ctx, .swap);
1125 }1182 }
11261183
...@@ -1142,6 +1199,9 @@ pub fn ArrayHashMapUnmanaged(...@@ -1142,6 +1199,9 @@ pub fn ArrayHashMapUnmanaged(
1142 return self.orderedRemoveContextAdapted(key, ctx, undefined);1199 return self.orderedRemoveContextAdapted(key, ctx, undefined);
1143 }1200 }
1144 pub fn orderedRemoveContextAdapted(self: *Self, key: anytype, key_ctx: anytype, ctx: Context) bool {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 return self.removeByKey(key, key_ctx, if (store_hash) {} else ctx, .ordered);1205 return self.removeByKey(key, key_ctx, if (store_hash) {} else ctx, .ordered);
1146 }1206 }
11471207
...@@ -1154,6 +1214,9 @@ pub fn ArrayHashMapUnmanaged(...@@ -1154,6 +1214,9 @@ pub fn ArrayHashMapUnmanaged(
1154 return self.swapRemoveAtContext(index, undefined);1214 return self.swapRemoveAtContext(index, undefined);
1155 }1215 }
1156 pub fn swapRemoveAtContext(self: *Self, index: usize, ctx: Context) void {1216 pub fn swapRemoveAtContext(self: *Self, index: usize, ctx: Context) void {
1217 self.pointer_stability.lock();
1218 defer self.pointer_stability.unlock();
1219
1157 self.removeByIndex(index, if (store_hash) {} else ctx, .swap);1220 self.removeByIndex(index, if (store_hash) {} else ctx, .swap);
1158 }1221 }
11591222
...@@ -1167,6 +1230,9 @@ pub fn ArrayHashMapUnmanaged(...@@ -1167,6 +1230,9 @@ pub fn ArrayHashMapUnmanaged(
1167 return self.orderedRemoveAtContext(index, undefined);1230 return self.orderedRemoveAtContext(index, undefined);
1168 }1231 }
1169 pub fn orderedRemoveAtContext(self: *Self, index: usize, ctx: Context) void {1232 pub fn orderedRemoveAtContext(self: *Self, index: usize, ctx: Context) void {
1233 self.pointer_stability.lock();
1234 defer self.pointer_stability.unlock();
1235
1170 self.removeByIndex(index, if (store_hash) {} else ctx, .ordered);1236 self.removeByIndex(index, if (store_hash) {} else ctx, .ordered);
1171 }1237 }
11721238
...@@ -1196,6 +1262,7 @@ pub fn ArrayHashMapUnmanaged(...@@ -1196,6 +1262,7 @@ pub fn ArrayHashMapUnmanaged(
1196 /// Set the map to an empty state, making deinitialization a no-op, and1262 /// Set the map to an empty state, making deinitialization a no-op, and
1197 /// returning a copy of the original.1263 /// returning a copy of the original.
1198 pub fn move(self: *Self) Self {1264 pub fn move(self: *Self) Self {
1265 self.pointer_stability.assertUnlocked();
1199 const result = self.*;1266 const result = self.*;
1200 self.* = .{};1267 self.* = .{};
1201 return result;1268 return result;
...@@ -1271,6 +1338,9 @@ pub fn ArrayHashMapUnmanaged(...@@ -1271,6 +1338,9 @@ pub fn ArrayHashMapUnmanaged(
1271 sort_ctx: anytype,1338 sort_ctx: anytype,
1272 ctx: Context,1339 ctx: Context,
1273 ) void {1340 ) void {
1341 self.pointer_stability.lock();
1342 defer self.pointer_stability.unlock();
1343
1274 switch (mode) {1344 switch (mode) {
1275 .stable => self.entries.sort(sort_ctx),1345 .stable => self.entries.sort(sort_ctx),
1276 .unstable => self.entries.sortUnstable(sort_ctx),1346 .unstable => self.entries.sortUnstable(sort_ctx),
...@@ -1288,6 +1358,9 @@ pub fn ArrayHashMapUnmanaged(...@@ -1288,6 +1358,9 @@ pub fn ArrayHashMapUnmanaged(
1288 return self.shrinkRetainingCapacityContext(new_len, undefined);1358 return self.shrinkRetainingCapacityContext(new_len, undefined);
1289 }1359 }
1290 pub fn shrinkRetainingCapacityContext(self: *Self, new_len: usize, ctx: Context) void {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 // Remove index entries from the new length onwards.1364 // Remove index entries from the new length onwards.
1292 // Explicitly choose to ONLY remove index entries and not the underlying array list1365 // Explicitly choose to ONLY remove index entries and not the underlying array list
1293 // entries as we're going to remove them in the subsequent shrink call.1366 // entries as we're going to remove them in the subsequent shrink call.
...@@ -1307,6 +1380,9 @@ pub fn ArrayHashMapUnmanaged(...@@ -1307,6 +1380,9 @@ pub fn ArrayHashMapUnmanaged(
1307 return self.shrinkAndFreeContext(allocator, new_len, undefined);1380 return self.shrinkAndFreeContext(allocator, new_len, undefined);
1308 }1381 }
1309 pub fn shrinkAndFreeContext(self: *Self, allocator: Allocator, new_len: usize, ctx: Context) void {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 // Remove index entries from the new length onwards.1386 // Remove index entries from the new length onwards.
1311 // Explicitly choose to ONLY remove index entries and not the underlying array list1387 // Explicitly choose to ONLY remove index entries and not the underlying array list
1312 // entries as we're going to remove them in the subsequent shrink call.1388 // entries as we're going to remove them in the subsequent shrink call.
...@@ -1325,6 +1401,9 @@ pub fn ArrayHashMapUnmanaged(...@@ -1325,6 +1401,9 @@ pub fn ArrayHashMapUnmanaged(
1325 return self.popContext(undefined);1401 return self.popContext(undefined);
1326 }1402 }
1327 pub fn popContext(self: *Self, ctx: Context) KV {1403 pub fn popContext(self: *Self, ctx: Context) KV {
1404 self.pointer_stability.lock();
1405 defer self.pointer_stability.unlock();
1406
1328 const item = self.entries.get(self.entries.len - 1);1407 const item = self.entries.get(self.entries.len - 1);
1329 if (self.index_header) |header|1408 if (self.index_header) |header|
1330 self.removeFromIndexByIndex(self.entries.len - 1, if (store_hash) {} else ctx, header);1409 self.removeFromIndexByIndex(self.entries.len - 1, if (store_hash) {} else ctx, header);
...@@ -1346,9 +1425,13 @@ pub fn ArrayHashMapUnmanaged(...@@ -1346,9 +1425,13 @@ pub fn ArrayHashMapUnmanaged(
1346 return if (self.entries.len == 0) null else self.popContext(ctx);1425 return if (self.entries.len == 0) null else self.popContext(ctx);
1347 }1426 }
13481427
1349 // ------------------ No pub fns below this point ------------------1428 fn fetchRemoveByKey(
13501429 self: *Self,
1351 fn fetchRemoveByKey(self: *Self, key: anytype, key_ctx: anytype, ctx: ByIndexContext, comptime removal_type: RemovalType) ?KV {1430 key: anytype,
1431 key_ctx: anytype,
1432 ctx: ByIndexContext,
1433 comptime removal_type: RemovalType,
1434 ) ?KV {
1352 const header = self.index_header orelse {1435 const header = self.index_header orelse {
1353 // Linear scan.1436 // Linear scan.
1354 const key_hash = if (store_hash) key_ctx.hash(key) else {};1437 const key_hash = if (store_hash) key_ctx.hash(key) else {};
...@@ -1377,7 +1460,15 @@ pub fn ArrayHashMapUnmanaged(...@@ -1377,7 +1460,15 @@ pub fn ArrayHashMapUnmanaged(
1377 .u32 => self.fetchRemoveByKeyGeneric(key, key_ctx, ctx, header, u32, removal_type),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 const indexes = header.indexes(I);1472 const indexes = header.indexes(I);
1382 const entry_index = self.removeFromIndexByKey(key, key_ctx, header, I, indexes) orelse return null;1473 const entry_index = self.removeFromIndexByKey(key, key_ctx, header, I, indexes) orelse return null;
1383 const slice = self.entries.slice();1474 const slice = self.entries.slice();
...@@ -1389,7 +1480,13 @@ pub fn ArrayHashMapUnmanaged(...@@ -1389,7 +1480,13 @@ pub fn ArrayHashMapUnmanaged(
1389 return removed_entry;1480 return removed_entry;
1390 }1481 }
13911482
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 const header = self.index_header orelse {1490 const header = self.index_header orelse {
1394 // Linear scan.1491 // Linear scan.
1395 const key_hash = if (store_hash) key_ctx.hash(key) else {};1492 const key_hash = if (store_hash) key_ctx.hash(key) else {};
lib/std/debug.zig+23
...@@ -2838,6 +2838,29 @@ pub fn ConfigurableTrace(comptime size: usize, comptime stack_frame_count: usize...@@ -2838,6 +2838,29 @@ pub fn ConfigurableTrace(comptime size: usize, comptime stack_frame_count: usize
2838 };2838 };
2839}2839}
28402840
2841pub const SafetyLock = struct {
2842 state: State = .unlocked,
2843
2844 pub const State = if (runtime_safety) enum { unlocked, locked } else enum { unlocked };
2845
2846 pub fn lock(l: *SafetyLock) void {
2847 if (!runtime_safety) return;
2848 assert(l.state == .unlocked);
2849 l.state = .locked;
2850 }
2851
2852 pub fn unlock(l: *SafetyLock) void {
2853 if (!runtime_safety) return;
2854 assert(l.state == .locked);
2855 l.state = .unlocked;
2856 }
2857
2858 pub fn assertUnlocked(l: SafetyLock) void {
2859 if (!runtime_safety) return;
2860 assert(l.state == .unlocked);
2861 }
2862};
2863
2841test {2864test {
2842 _ = &dump_hex;2865 _ = &dump_hex;
2843}2866}
lib/std/hash_map.zig+78-26
...@@ -416,6 +416,23 @@ pub fn HashMap(...@@ -416,6 +416,23 @@ pub fn HashMap(
416 };416 };
417 }417 }
418418
419 /// Puts the hash map into a state where any method call that would
420 /// cause an existing key or value pointer to become invalidated will
421 /// instead trigger an assertion.
422 ///
423 /// An additional call to `lockPointers` in such state also triggers an
424 /// assertion.
425 ///
426 /// `unlockPointers` returns the hash map to the previous state.
427 pub fn lockPointers(self: *Self) void {
428 self.unmanaged.lockPointers();
429 }
430
431 /// Undoes a call to `lockPointers`.
432 pub fn unlockPointers(self: *Self) void {
433 self.unmanaged.unlockPointers();
434 }
435
419 /// Release the backing array and invalidate this map.436 /// Release the backing array and invalidate this map.
420 /// This does *not* deinit keys, values, or the context!437 /// This does *not* deinit keys, values, or the context!
421 /// If your keys or values need to be released, ensure438 /// If your keys or values need to be released, ensure
...@@ -672,6 +689,7 @@ pub fn HashMap(...@@ -672,6 +689,7 @@ pub fn HashMap(
672 /// Set the map to an empty state, making deinitialization a no-op, and689 /// Set the map to an empty state, making deinitialization a no-op, and
673 /// returning a copy of the original.690 /// returning a copy of the original.
674 pub fn move(self: *Self) Self {691 pub fn move(self: *Self) Self {
692 self.unmanaged.pointer_stability.assertUnlocked();
675 const result = self.*;693 const result = self.*;
676 self.unmanaged = .{};694 self.unmanaged = .{};
677 return result;695 return result;
...@@ -722,6 +740,9 @@ pub fn HashMapUnmanaged(...@@ -722,6 +740,9 @@ pub fn HashMapUnmanaged(
722 /// `max_load_percentage`.740 /// `max_load_percentage`.
723 available: Size = 0,741 available: Size = 0,
724742
743 /// Used to detect memory safety violations.
744 pointer_stability: std.debug.SafetyLock = .{},
745
725 // This is purely empirical and not a /very smart magic constant™/.746 // This is purely empirical and not a /very smart magic constant™/.
726 /// Capacity of the first grow when bootstrapping the hashmap.747 /// Capacity of the first grow when bootstrapping the hashmap.
727 const minimal_capacity = 8;748 const minimal_capacity = 8;
...@@ -884,11 +905,29 @@ pub fn HashMapUnmanaged(...@@ -884,11 +905,29 @@ pub fn HashMapUnmanaged(
884 };905 };
885 }906 }
886907
908 /// Puts the hash map into a state where any method call that would
909 /// cause an existing key or value pointer to become invalidated will
910 /// instead trigger an assertion.
911 ///
912 /// An additional call to `lockPointers` in such state also triggers an
913 /// assertion.
914 ///
915 /// `unlockPointers` returns the hash map to the previous state.
916 pub fn lockPointers(self: *Self) void {
917 self.pointer_stability.lock();
918 }
919
920 /// Undoes a call to `lockPointers`.
921 pub fn unlockPointers(self: *Self) void {
922 self.pointer_stability.unlock();
923 }
924
887 fn isUnderMaxLoadPercentage(size: Size, cap: Size) bool {925 fn isUnderMaxLoadPercentage(size: Size, cap: Size) bool {
888 return size * 100 < max_load_percentage * cap;926 return size * 100 < max_load_percentage * cap;
889 }927 }
890928
891 pub fn deinit(self: *Self, allocator: Allocator) void {929 pub fn deinit(self: *Self, allocator: Allocator) void {
930 self.pointer_stability.assertUnlocked();
892 self.deallocate(allocator);931 self.deallocate(allocator);
893 self.* = undefined;932 self.* = undefined;
894 }933 }
...@@ -905,6 +944,8 @@ pub fn HashMapUnmanaged(...@@ -905,6 +944,8 @@ pub fn HashMapUnmanaged(
905 return ensureTotalCapacityContext(self, allocator, new_size, undefined);944 return ensureTotalCapacityContext(self, allocator, new_size, undefined);
906 }945 }
907 pub fn ensureTotalCapacityContext(self: *Self, allocator: Allocator, new_size: Size, ctx: Context) Allocator.Error!void {946 pub fn ensureTotalCapacityContext(self: *Self, allocator: Allocator, new_size: Size, ctx: Context) Allocator.Error!void {
947 self.pointer_stability.lock();
948 defer self.pointer_stability.unlock();
908 if (new_size > self.size)949 if (new_size > self.size)
909 try self.growIfNeeded(allocator, new_size - self.size, ctx);950 try self.growIfNeeded(allocator, new_size - self.size, ctx);
910 }951 }
...@@ -919,14 +960,18 @@ pub fn HashMapUnmanaged(...@@ -919,14 +960,18 @@ pub fn HashMapUnmanaged(
919 }960 }
920961
921 pub fn clearRetainingCapacity(self: *Self) void {962 pub fn clearRetainingCapacity(self: *Self) void {
963 self.pointer_stability.lock();
964 defer self.pointer_stability.unlock();
922 if (self.metadata) |_| {965 if (self.metadata) |_| {
923 self.initMetadatas();966 self.initMetadatas();
924 self.size = 0;967 self.size = 0;
925 self.available = @as(u32, @truncate((self.capacity() * max_load_percentage) / 100));968 self.available = @truncate((self.capacity() * max_load_percentage) / 100);
926 }969 }
927 }970 }
928971
929 pub fn clearAndFree(self: *Self, allocator: Allocator) void {972 pub fn clearAndFree(self: *Self, allocator: Allocator) void {
973 self.pointer_stability.lock();
974 defer self.pointer_stability.unlock();
930 self.deallocate(allocator);975 self.deallocate(allocator);
931 self.size = 0;976 self.size = 0;
932 self.available = 0;977 self.available = 0;
...@@ -997,9 +1042,11 @@ pub fn HashMapUnmanaged(...@@ -997,9 +1042,11 @@ pub fn HashMapUnmanaged(
997 return self.putNoClobberContext(allocator, key, value, undefined);1042 return self.putNoClobberContext(allocator, key, value, undefined);
998 }1043 }
999 pub fn putNoClobberContext(self: *Self, allocator: Allocator, key: K, value: V, ctx: Context) Allocator.Error!void {1044 pub fn putNoClobberContext(self: *Self, allocator: Allocator, key: K, value: V, ctx: Context) Allocator.Error!void {
1000 assert(!self.containsContext(key, ctx));1045 {
1001 try self.growIfNeeded(allocator, 1, ctx);1046 self.pointer_stability.lock();
10021047 defer self.pointer_stability.unlock();
1048 try self.growIfNeeded(allocator, 1, ctx);
1049 }
1003 self.putAssumeCapacityNoClobberContext(key, value, ctx);1050 self.putAssumeCapacityNoClobberContext(key, value, ctx);
1004 }1051 }
10051052
...@@ -1028,7 +1075,7 @@ pub fn HashMapUnmanaged(...@@ -1028,7 +1075,7 @@ pub fn HashMapUnmanaged(
10281075
1029 const hash = ctx.hash(key);1076 const hash = ctx.hash(key);
1030 const mask = self.capacity() - 1;1077 const mask = self.capacity() - 1;
1031 var idx = @as(usize, @truncate(hash & mask));1078 var idx: usize = @truncate(hash & mask);
10321079
1033 var metadata = self.metadata.? + idx;1080 var metadata = self.metadata.? + idx;
1034 while (metadata[0].isUsed()) {1081 while (metadata[0].isUsed()) {
...@@ -1280,17 +1327,21 @@ pub fn HashMapUnmanaged(...@@ -1280,17 +1327,21 @@ pub fn HashMapUnmanaged(
1280 return self.getOrPutContextAdapted(allocator, key, key_ctx, undefined);1327 return self.getOrPutContextAdapted(allocator, key, key_ctx, undefined);
1281 }1328 }
1282 pub fn getOrPutContextAdapted(self: *Self, allocator: Allocator, key: anytype, key_ctx: anytype, ctx: Context) Allocator.Error!GetOrPutResult {1329 pub fn getOrPutContextAdapted(self: *Self, allocator: Allocator, key: anytype, key_ctx: anytype, ctx: Context) Allocator.Error!GetOrPutResult {
1283 self.growIfNeeded(allocator, 1, ctx) catch |err| {1330 {
1284 // If allocation fails, try to do the lookup anyway.1331 self.pointer_stability.lock();
1285 // If we find an existing item, we can return it.1332 defer self.pointer_stability.unlock();
1286 // Otherwise return the error, we could not add another.1333 self.growIfNeeded(allocator, 1, ctx) catch |err| {
1287 const index = self.getIndex(key, key_ctx) orelse return err;1334 // If allocation fails, try to do the lookup anyway.
1288 return GetOrPutResult{1335 // If we find an existing item, we can return it.
1289 .key_ptr = &self.keys()[index],1336 // Otherwise return the error, we could not add another.
1290 .value_ptr = &self.values()[index],1337 const index = self.getIndex(key, key_ctx) orelse return err;
1291 .found_existing = true,1338 return GetOrPutResult{
1339 .key_ptr = &self.keys()[index],
1340 .value_ptr = &self.values()[index],
1341 .found_existing = true,
1342 };
1292 };1343 };
1293 };1344 }
1294 return self.getOrPutAssumeCapacityAdapted(key, key_ctx);1345 return self.getOrPutAssumeCapacityAdapted(key, key_ctx);
1295 }1346 }
12961347
...@@ -1495,6 +1546,7 @@ pub fn HashMapUnmanaged(...@@ -1495,6 +1546,7 @@ pub fn HashMapUnmanaged(
1495 /// Set the map to an empty state, making deinitialization a no-op, and1546 /// Set the map to an empty state, making deinitialization a no-op, and
1496 /// returning a copy of the original.1547 /// returning a copy of the original.
1497 pub fn move(self: *Self) Self {1548 pub fn move(self: *Self) Self {
1549 self.pointer_stability.assertUnlocked();
1498 const result = self.*;1550 const result = self.*;
1499 self.* = .{};1551 self.* = .{};
1500 return result;1552 return result;
...@@ -1506,28 +1558,28 @@ pub fn HashMapUnmanaged(...@@ -1506,28 +1558,28 @@ pub fn HashMapUnmanaged(
1506 assert(new_cap > self.capacity());1558 assert(new_cap > self.capacity());
1507 assert(std.math.isPowerOfTwo(new_cap));1559 assert(std.math.isPowerOfTwo(new_cap));
15081560
1509 var map = Self{};1561 var map: Self = .{};
1510 defer map.deinit(allocator);1562 defer map.deinit(allocator);
1563 map.pointer_stability.lock();
1511 try map.allocate(allocator, new_cap);1564 try map.allocate(allocator, new_cap);
1512 map.initMetadatas();1565 map.initMetadatas();
1513 map.available = @truncate((new_cap * max_load_percentage) / 100);1566 map.available = @truncate((new_cap * max_load_percentage) / 100);
15141567
1515 if (self.size != 0) {1568 if (self.size != 0) {
1516 const old_capacity = self.capacity();1569 const old_capacity = self.capacity();
1517 var i: Size = 0;1570 for (
1518 var metadata = self.metadata.?;1571 self.metadata.?[0..old_capacity],
1519 const keys_ptr = self.keys();1572 self.keys()[0..old_capacity],
1520 const values_ptr = self.values();1573 self.values()[0..old_capacity],
1521 while (i < old_capacity) : (i += 1) {1574 ) |m, k, v| {
1522 if (metadata[i].isUsed()) {1575 if (!m.isUsed()) continue;
1523 map.putAssumeCapacityNoClobberContext(keys_ptr[i], values_ptr[i], ctx);1576 map.putAssumeCapacityNoClobberContext(k, v, ctx);
1524 if (map.size == self.size)1577 if (map.size == self.size) break;
1525 break;
1526 }
1527 }1578 }
1528 }1579 }
15291580
1530 self.size = 0;1581 self.size = 0;
1582 self.pointer_stability = .{ .state = .unlocked };
1531 std.mem.swap(Self, self, &map);1583 std.mem.swap(Self, self, &map);
1532 }1584 }
15331585