authorgravatar for jacobly@ziglang.orgJacob Young <jacobly@ziglang.org> 2024-06-15 19:58:29-04:00
committergravatar for jacobly@ziglang.orgJacob Young <jacobly@ziglang.org> 2024-07-07 22:59:52-04:00
logc8b9364b30adfdd1716b20428a3d934eac75cc87
tree71d33b680a494ec8fe46cd616c81ed60d9ab9a17
parentcda716ecc43929fd1c2c9679335b8b22f1b67d1a

InternPool: use thread-safe hash map for strings


1 files changed, 254 insertions(+), 130 deletions(-)

src/InternPool.zig+254-130
......@@ -46,14 +46,6 @@ namespaces_free_list: std.ArrayListUnmanaged(NamespaceIndex) = .{},
4646/// These are not serialized; it is computed upon deserialization.
4747maps: std.ArrayListUnmanaged(FieldMap) = .{},
4848
49/// Used for finding the index inside `string_bytes`.
50string_table: std.HashMapUnmanaged(
51 u32,
52 void,
53 std.hash_map.StringIndexContext,
54 std.hash_map.default_max_load_percentage,
55) = .{},
56
5749/// An index into `tracked_insts` gives a reference to a single ZIR instruction which
5850/// persists across incremental updates.
5951tracked_insts: std.AutoArrayHashMapUnmanaged(TrackedInst, void) = .{},
......@@ -358,22 +350,31 @@ const Local = struct {
358350 /// node: Garbage.Node,
359351 /// header: List.Header,
360352 /// data: [capacity]u32,
361 /// tag: [capacity]Tag,
353 /// tag: [header.capacity]Tag,
362354 items: List,
363355
364356 /// node: Garbage.Node,
365357 /// header: List.Header,
366 /// extra: [capacity]u32,
358 /// extra: [header.capacity]u32,
367359 extra: List,
368360
361 /// node: Garbage.Node,
362 /// header: List.Header,
363 /// bytes: [header.capacity]u8,
364 strings: List,
365
369366 garbage: Garbage,
370367
371368 const List = struct {
372369 entries: [*]u32,
373370
374 const empty: List = .{
375 .entries = @constCast(&[_]u32{ 0, 0 })[Header.fields_len..].ptr,
376 };
371 const empty: List = .{ .entries = @constCast(&(extern struct {
372 header: Header,
373 entries: [0]u32,
374 }{
375 .header = .{ .len = 0, .capacity = 0 },
376 .entries = .{},
377 }).entries) };
377378
378379 fn acquire(list: *const List) List {
379380 return .{ .entries = @atomicLoad([*]u32, &list.entries, .acquire) };
......@@ -402,63 +403,75 @@ const Local = struct {
402403};
403404
404405const Shard = struct {
405 aligned: void align(std.atomic.cache_line) = {},
406
407 mutate_mutex: std.Thread.Mutex.Recursive,
408
409 /// node: Local.Garbage.Node,
410 /// header: Map.Header,
411 /// entries: [capacity]Map.Entry,
412 map: Map,
406 shared: struct {
407 map: Map(Index),
408 string_map: Map(OptionalNullTerminatedString),
409 } align(std.atomic.cache_line),
410 mutate: struct {
411 // TODO: measure cost of sharing unrelated mutate state
412 map: Mutate align(std.atomic.cache_line),
413 string_map: Mutate align(std.atomic.cache_line),
414 },
413415
414 const Map = struct {
415 entries: [*]u32,
416 const Mutate = struct {
417 mutex: std.Thread.Mutex.Recursive,
418 len: u32,
416419
417 const empty: Map = .{
418 .entries = @constCast(&[_]u32{ 0, 1, @intFromEnum(Index.none), 0 })[Header.fields_len..].ptr,
420 const empty: Mutate = .{
421 .mutex = std.Thread.Mutex.Recursive.init,
422 .len = 0,
419423 };
424 };
420425
421 fn acquire(map: *const Map) Map {
422 return .{ .entries = @atomicLoad([*]u32, &map.entries, .acquire) };
423 }
424 fn release(map: *Map, new_map: Map) void {
425 @atomicStore([*]u32, &map.entries, new_map.entries, .release);
426 }
427
428 const Header = extern struct {
429 len: u32,
430 capacity: u32,
426 fn Map(comptime Value: type) type {
427 comptime assert(@typeInfo(Value).Enum.tag_type == u32);
428 _ = @as(Value, .none); // expected .none key
429 return struct {
430 /// node: Local.Garbage.Node,
431 /// header: Header,
432 /// entries: [header.capacity]Entry,
433 entries: [*]Entry,
434
435 const empty: @This() = .{ .entries = @constCast(&(extern struct {
436 header: Header,
437 entries: [1]Entry,
438 }{
439 .header = .{ .capacity = 1 },
440 .entries = .{.{ .value = .none, .hash = undefined }},
441 }).entries) };
442
443 fn acquire(map: *const @This()) @This() {
444 return .{ .entries = @atomicLoad([*]Entry, &map.entries, .acquire) };
445 }
446 fn release(map: *@This(), new_map: @This()) void {
447 @atomicStore([*]Entry, &map.entries, new_map.entries, .release);
448 }
431449
432 const fields_len: u32 = @typeInfo(Header).Struct.fields.len;
450 const Header = extern struct {
451 capacity: u32,
433452
434 fn mask(head: *const Header) u32 {
435 assert(std.math.isPowerOfTwo(head.capacity));
436 assert(std.math.isPowerOfTwo(Entry.fields_len));
437 return (head.capacity - 1) * Entry.fields_len;
453 fn mask(head: *const Header) u32 {
454 assert(std.math.isPowerOfTwo(head.capacity));
455 return head.capacity - 1;
456 }
457 };
458 fn header(map: @This()) *Header {
459 return &(@as([*]Header, @ptrCast(map.entries)) - 1)[0];
438460 }
439 };
440 fn header(map: Map) *Header {
441 return @ptrCast(map.entries - Header.fields_len);
442 }
443461
444 const Entry = extern struct {
445 index: Index,
446 hash: u32,
462 const Entry = extern struct {
463 value: Value,
464 hash: u32,
447465
448 const fields_len: u32 = @typeInfo(Entry).Struct.fields.len;
449
450 fn acquire(entry: *const Entry) Index {
451 return @atomicLoad(Index, &entry.index, .acquire);
452 }
453 fn release(entry: *Entry, index: Index) void {
454 @atomicStore(Index, &entry.index, index, .release);
455 }
466 fn acquire(entry: *const Entry) Value {
467 return @atomicLoad(Value, &entry.value, .acquire);
468 }
469 fn release(entry: *Entry, value: Value) void {
470 @atomicStore(Value, &entry.value, value, .release);
471 }
472 };
456473 };
457 fn at(map: Map, index: usize) *Entry {
458 assert(index % Entry.fields_len == 0);
459 return @ptrCast(&map.entries[index]);
460 }
461 };
474 }
462475};
463476
464477const FieldMap = std.ArrayHashMapUnmanaged(void, void, std.array_hash_map.AutoContext(void), false);
......@@ -618,9 +631,13 @@ pub const NullTerminatedString = enum(u32) {
618631 return @enumFromInt(@intFromEnum(self));
619632 }
620633
634 fn toOverlongSlice(string: NullTerminatedString, ip: *const InternPool) []const u8 {
635 return ip.string_bytes.items[@intFromEnum(string)..];
636 }
637
621638 pub fn toSlice(string: NullTerminatedString, ip: *const InternPool) [:0]const u8 {
622 const slice = ip.string_bytes.items[@intFromEnum(string)..];
623 return slice[0..std.mem.indexOfScalar(u8, slice, 0).? :0];
639 const overlong_slice = string.toOverlongSlice(ip);
640 return overlong_slice[0..std.mem.indexOfScalar(u8, overlong_slice, 0).? :0];
624641 }
625642
626643 pub fn length(string: NullTerminatedString, ip: *const InternPool) u32 {
......@@ -628,7 +645,10 @@ pub const NullTerminatedString = enum(u32) {
628645 }
629646
630647 pub fn eqlSlice(string: NullTerminatedString, slice: []const u8, ip: *const InternPool) bool {
631 return std.mem.eql(u8, string.toSlice(ip), slice);
648 const overlong_slice = string.toOverlongSlice(ip);
649 return overlong_slice.len > slice.len and
650 std.mem.eql(u8, overlong_slice[0..slice.len], slice) and
651 overlong_slice[slice.len] == 0;
632652 }
633653
634654 const Adapter = struct {
......@@ -4639,14 +4659,21 @@ pub fn init(ip: *InternPool, gpa: Allocator, total_threads: usize) !void {
46394659 @memset(ip.local, .{
46404660 .items = Local.List.empty,
46414661 .extra = Local.List.empty,
4662 .strings = Local.List.empty,
46424663 .garbage = .{},
46434664 });
46444665
46454666 ip.shard_shift = @intCast(std.math.log2_int_ceil(usize, total_threads));
46464667 ip.shards = try gpa.alloc(Shard, @as(usize, 1) << ip.shard_shift);
46474668 @memset(ip.shards, .{
4648 .mutate_mutex = std.Thread.Mutex.Recursive.init,
4649 .map = Shard.Map.empty,
4669 .shared = .{
4670 .map = Shard.Map(Index).empty,
4671 .string_map = Shard.Map(OptionalNullTerminatedString).empty,
4672 },
4673 .mutate = .{
4674 .map = Shard.Mutate.empty,
4675 .string_map = Shard.Mutate.empty,
4676 },
46504677 });
46514678
46524679 // Reserve string index 0 for an empty string.
......@@ -4697,8 +4724,6 @@ pub fn deinit(ip: *InternPool, gpa: Allocator) void {
46974724 for (ip.maps.items) |*map| map.deinit(gpa);
46984725 ip.maps.deinit(gpa);
46994726
4700 ip.string_table.deinit(gpa);
4701
47024727 ip.tracked_insts.deinit(gpa);
47034728
47044729 ip.src_hash_deps.deinit(gpa);
......@@ -5363,9 +5388,9 @@ const GetOrPutKey = union(enum) {
53635388 switch (gop.*) {
53645389 .existing => unreachable,
53655390 .new => |info| {
5366 info.shard.map.at(info.map_index).release(index);
5367 info.shard.map.header().len += 1;
5368 info.shard.mutate_mutex.unlock();
5391 info.shard.shared.map.entries[info.map_index].release(index);
5392 info.shard.mutate.map.len += 1;
5393 info.shard.mutate.map.mutex.unlock();
53695394 },
53705395 }
53715396 gop.* = .{ .existing = index };
......@@ -5380,7 +5405,7 @@ const GetOrPutKey = union(enum) {
53805405 fn deinit(gop: *GetOrPutKey) void {
53815406 switch (gop.*) {
53825407 .existing => {},
5383 .new => |info| info.shard.mutate_mutex.unlock(),
5408 .new => |info| info.shard.mutate.map.mutex.unlock(),
53845409 }
53855410 gop.* = undefined;
53865411 }
......@@ -5394,70 +5419,69 @@ fn getOrPutKey(
53945419 const full_hash = key.hash64(ip);
53955420 const hash: u32 = @truncate(full_hash >> 32);
53965421 const shard = &ip.shards[@intCast(full_hash & (ip.shards.len - 1))];
5397 var map = shard.map.acquire();
5422 var map = shard.shared.map.acquire();
5423 const Map = @TypeOf(map);
53985424 var map_mask = map.header().mask();
53995425 var map_index = hash;
5400 while (true) : (map_index += Shard.Map.Entry.fields_len) {
5426 while (true) : (map_index += 1) {
54015427 map_index &= map_mask;
5402 const entry = map.at(map_index);
5428 const entry = &map.entries[map_index];
54035429 const index = entry.acquire();
54045430 if (index == .none) break;
5405 if (entry.hash == hash and ip.indexToKey(index).eql(key, ip))
5406 return .{ .existing = index };
5431 if (entry.hash != hash) continue;
5432 if (ip.indexToKey(index).eql(key, ip)) return .{ .existing = index };
54075433 }
5408 shard.mutate_mutex.lock();
5409 errdefer shard.mutate_mutex.unlock();
5410 if (map.entries != shard.map.entries) {
5411 map = shard.map;
5434 shard.mutate.map.mutex.lock();
5435 errdefer shard.mutate.map.mutex.unlock();
5436 if (map.entries != shard.shared.map.entries) {
5437 map = shard.shared.map;
54125438 map_mask = map.header().mask();
54135439 map_index = hash;
54145440 }
5415 while (true) : (map_index += Shard.Map.Entry.fields_len) {
5441 while (true) : (map_index += 1) {
54165442 map_index &= map_mask;
5417 const entry = map.at(map_index);
5418 const index = entry.index;
5443 const entry = &map.entries[map_index];
5444 const index = entry.value;
54195445 if (index == .none) break;
5420 if (entry.hash == hash and ip.indexToKey(index).eql(key, ip)) {
5421 defer shard.mutate_mutex.unlock();
5446 if (entry.hash != hash) continue;
5447 if (ip.indexToKey(index).eql(key, ip)) {
5448 defer shard.mutate.map.mutex.unlock();
54225449 return .{ .existing = index };
54235450 }
54245451 }
54255452 const map_header = map.header().*;
5426 if (map_header.len >= map_header.capacity * 3 / 5) {
5453 if (shard.mutate.map.len >= map_header.capacity * 3 / 5) {
54275454 const new_map_capacity = map_header.capacity * 2;
54285455 const new_map_buf = try gpa.alignedAlloc(
54295456 u8,
54305457 Local.garbage_align,
5431 @sizeOf(Local.Garbage.Node) + (Shard.Map.Header.fields_len +
5432 new_map_capacity * Shard.Map.Entry.fields_len) * @sizeOf(u32),
5458 @sizeOf(Local.Garbage.Node) + @sizeOf(Map.Header) +
5459 new_map_capacity * @sizeOf(Map.Entry),
54335460 );
54345461 const new_node: *Local.Garbage.Node = @ptrCast(new_map_buf.ptr);
54355462 new_node.* = .{ .data = .{ .buf_len = new_map_buf.len } };
54365463 ip.local[@intFromEnum(tid)].garbage.prepend(new_node);
54375464 const new_map_entries = std.mem.bytesAsSlice(
5438 u32,
5439 new_map_buf[@sizeOf(Local.Garbage.Node)..],
5440 )[Shard.Map.Header.fields_len..];
5441 const new_map: Shard.Map = .{ .entries = new_map_entries.ptr };
5442 new_map.header().* = .{
5443 .len = map_header.len,
5444 .capacity = new_map_capacity,
5445 };
5446 @memset(new_map_entries, @intFromEnum(Index.none));
5465 Map.Entry,
5466 new_map_buf[@sizeOf(Local.Garbage.Node) + @sizeOf(Map.Header) ..],
5467 );
5468 const new_map: Map = .{ .entries = new_map_entries.ptr };
5469 new_map.header().* = .{ .capacity = new_map_capacity };
5470 @memset(new_map_entries, .{ .value = .none, .hash = undefined });
54475471 const new_map_mask = new_map.header().mask();
54485472 map_index = 0;
5449 while (map_index < map_header.capacity * 2) : (map_index += Shard.Map.Entry.fields_len) {
5450 const entry = map.at(map_index);
5451 const index = entry.index;
5473 while (map_index < map_header.capacity) : (map_index += 1) {
5474 const entry = &map.entries[map_index];
5475 const index = entry.value;
54525476 if (index == .none) continue;
54535477 const item_hash = entry.hash;
54545478 var new_map_index = item_hash;
5455 while (true) : (new_map_index += Shard.Map.Entry.fields_len) {
5479 while (true) : (new_map_index += 1) {
54565480 new_map_index &= new_map_mask;
5457 const new_entry = new_map.at(new_map_index);
5458 if (new_entry.index != .none) continue;
5481 const new_entry = &new_map.entries[new_map_index];
5482 if (new_entry.value != .none) continue;
54595483 new_entry.* = .{
5460 .index = index,
5484 .value = index,
54615485 .hash = item_hash,
54625486 };
54635487 break;
......@@ -5465,13 +5489,13 @@ fn getOrPutKey(
54655489 }
54665490 map = new_map;
54675491 map_index = hash;
5468 while (true) : (map_index += Shard.Map.Entry.fields_len) {
5492 while (true) : (map_index += 1) {
54695493 map_index &= new_map_mask;
5470 if (map.at(map_index).index == .none) break;
5494 if (map.entries[map_index].value == .none) break;
54715495 }
5472 shard.map.release(new_map);
5496 shard.shared.map.release(new_map);
54735497 }
5474 map.at(map_index).hash = hash;
5498 map.entries[map_index].hash = hash;
54755499 return .{ .new = .{ .shard = shard, .map_index = map_index } };
54765500}
54775501
......@@ -7689,22 +7713,19 @@ pub fn getIfExists(ip: *const InternPool, key: Key) ?Index {
76897713 const full_hash = key.hash64(ip);
76907714 const hash: u32 = @truncate(full_hash >> 32);
76917715 const shard = &ip.shards[@intCast(full_hash & (ip.shards.len - 1))];
7692 const map = shard.map.acquire();
7716 const map = shard.shared.map.acquire();
76937717 const map_mask = map.header().mask();
76947718 var map_index = hash;
7695 while (true) : (map_index += Shard.Map.Entry.fields_len) {
7719 while (true) : (map_index += 1) {
76967720 map_index &= map_mask;
7697 const entry = map.at(map_index);
7721 const entry = &map.entries[map_index];
76987722 const index = entry.acquire();
76997723 if (index == .none) return null;
7700 if (entry.hash == hash and ip.indexToKey(index).eql(key, ip)) return index;
7724 if (entry.hash != hash) continue;
7725 if (ip.indexToKey(index).eql(key, ip)) return index;
77017726 }
77027727}
77037728
7704pub fn getAssumeExists(ip: *const InternPool, key: Key) Index {
7705 return ip.getIfExists(key).?;
7706}
7707
77087729fn addStringsToMap(
77097730 ip: *InternPool,
77107731 map_index: MapIndex,
......@@ -8618,7 +8639,13 @@ fn dumpStatsFallible(ip: *const InternPool, arena: Allocator) anyerror!void {
86188639 .type_inferred_error_set => 0,
86198640 .type_enum_explicit, .type_enum_nonexhaustive => b: {
86208641 const info = ip.extraData(EnumExplicit, data);
8621 var ints = @typeInfo(EnumExplicit).Struct.fields.len + info.captures_len + info.fields_len;
8642 var ints = @typeInfo(EnumExplicit).Struct.fields.len;
8643 if (info.zir_index == .none) ints += 1;
8644 ints += if (info.captures_len != std.math.maxInt(u32))
8645 info.captures_len
8646 else
8647 @typeInfo(PackedU64).Struct.fields.len;
8648 ints += info.fields_len;
86228649 if (info.values_map != .none) ints += info.fields_len;
86238650 break :b @sizeOf(u32) * ints;
86248651 },
......@@ -9084,7 +9111,6 @@ pub fn getOrPutTrailingString(
90849111 len: usize,
90859112 comptime embedded_nulls: EmbeddedNulls,
90869113) Allocator.Error!embedded_nulls.StringType() {
9087 _ = tid;
90889114 const string_bytes = &ip.string_bytes;
90899115 const str_index: u32 = @intCast(string_bytes.items.len - len);
90909116 if (len > 0 and string_bytes.getLast() == 0) {
......@@ -9101,25 +9127,123 @@ pub fn getOrPutTrailingString(
91019127 return @enumFromInt(str_index);
91029128 },
91039129 }
9104 const gop = try ip.string_table.getOrPutContextAdapted(gpa, key, std.hash_map.StringIndexAdapter{
9105 .bytes = string_bytes,
9106 }, std.hash_map.StringIndexContext{
9107 .bytes = string_bytes,
9108 });
9109 if (gop.found_existing) {
9130 const maybe_existing_index = try ip.getOrPutStringValue(gpa, tid, key, @enumFromInt(str_index));
9131 if (maybe_existing_index.unwrap()) |existing_index| {
91109132 string_bytes.shrinkRetainingCapacity(str_index);
9111 return @enumFromInt(gop.key_ptr.*);
9133 return @enumFromInt(@intFromEnum(existing_index));
91129134 } else {
9113 gop.key_ptr.* = str_index;
91149135 string_bytes.appendAssumeCapacity(0);
91159136 return @enumFromInt(str_index);
91169137 }
91179138}
91189139
9119pub fn getString(ip: *InternPool, s: []const u8) OptionalNullTerminatedString {
9120 return if (ip.string_table.getKeyAdapted(s, std.hash_map.StringIndexAdapter{
9121 .bytes = &ip.string_bytes,
9122 })) |index| @enumFromInt(index) else .none;
9140fn getOrPutStringValue(
9141 ip: *InternPool,
9142 gpa: Allocator,
9143 tid: Zcu.PerThread.Id,
9144 key: []const u8,
9145 value: NullTerminatedString,
9146) Allocator.Error!OptionalNullTerminatedString {
9147 const full_hash = Hash.hash(0, key);
9148 const hash: u32 = @truncate(full_hash >> 32);
9149 const shard = &ip.shards[@intCast(full_hash & (ip.shards.len - 1))];
9150 var map = shard.shared.string_map.acquire();
9151 const Map = @TypeOf(map);
9152 var map_mask = map.header().mask();
9153 var map_index = hash;
9154 while (true) : (map_index += 1) {
9155 map_index &= map_mask;
9156 const entry = &map.entries[map_index];
9157 const index = entry.acquire().unwrap() orelse break;
9158 if (entry.hash != hash) continue;
9159 if (index.eqlSlice(key, ip)) return index.toOptional();
9160 }
9161 shard.mutate.string_map.mutex.lock();
9162 defer shard.mutate.string_map.mutex.unlock();
9163 if (map.entries != shard.shared.string_map.entries) {
9164 shard.mutate.string_map.len += 1;
9165 map = shard.shared.string_map;
9166 map_mask = map.header().mask();
9167 map_index = hash;
9168 }
9169 while (true) : (map_index += 1) {
9170 map_index &= map_mask;
9171 const entry = &map.entries[map_index];
9172 const index = entry.acquire().unwrap() orelse break;
9173 if (entry.hash != hash) continue;
9174 if (index.eqlSlice(key, ip)) return index.toOptional();
9175 }
9176 defer shard.mutate.string_map.len += 1;
9177 const map_header = map.header().*;
9178 if (shard.mutate.string_map.len < map_header.capacity * 3 / 5) {
9179 const entry = &map.entries[map_index];
9180 entry.hash = hash;
9181 entry.release(value.toOptional());
9182 return .none;
9183 }
9184 const new_map_capacity = map_header.capacity * 2;
9185 const new_map_buf = try gpa.alignedAlloc(
9186 u8,
9187 Local.garbage_align,
9188 @sizeOf(Local.Garbage.Node) + @sizeOf(Map.Header) +
9189 new_map_capacity * @sizeOf(Map.Entry),
9190 );
9191 const new_node: *Local.Garbage.Node = @ptrCast(new_map_buf.ptr);
9192 new_node.* = .{ .data = .{ .buf_len = new_map_buf.len } };
9193 ip.local[@intFromEnum(tid)].garbage.prepend(new_node);
9194 const new_map_entries = std.mem.bytesAsSlice(
9195 Map.Entry,
9196 new_map_buf[@sizeOf(Local.Garbage.Node) + @sizeOf(Map.Header) ..],
9197 );
9198 const new_map: Map = .{ .entries = new_map_entries.ptr };
9199 new_map.header().* = .{ .capacity = new_map_capacity };
9200 @memset(new_map_entries, .{ .value = .none, .hash = undefined });
9201 const new_map_mask = new_map.header().mask();
9202 map_index = 0;
9203 while (map_index < map_header.capacity) : (map_index += 1) {
9204 const entry = &map.entries[map_index];
9205 const index = entry.value.unwrap() orelse continue;
9206 const item_hash = entry.hash;
9207 var new_map_index = item_hash;
9208 while (true) : (new_map_index += 1) {
9209 new_map_index &= new_map_mask;
9210 const new_entry = &new_map.entries[new_map_index];
9211 if (new_entry.value != .none) continue;
9212 new_entry.* = .{
9213 .value = index.toOptional(),
9214 .hash = item_hash,
9215 };
9216 break;
9217 }
9218 }
9219 map = new_map;
9220 map_index = hash;
9221 while (true) : (map_index += 1) {
9222 map_index &= new_map_mask;
9223 if (map.entries[map_index].value == .none) break;
9224 }
9225 map.entries[map_index] = .{
9226 .value = value.toOptional(),
9227 .hash = hash,
9228 };
9229 shard.shared.string_map.release(new_map);
9230 return .none;
9231}
9232
9233pub fn getString(ip: *InternPool, key: []const u8) OptionalNullTerminatedString {
9234 const full_hash = Hash.hash(0, key);
9235 const hash: u32 = @truncate(full_hash >> 32);
9236 const shard = &ip.shards[@intCast(full_hash & (ip.shards.len - 1))];
9237 const map = shard.shared.string_map.acquire();
9238 const map_mask = map.header().mask();
9239 var map_index = hash;
9240 while (true) : (map_index += 1) {
9241 map_index &= map_mask;
9242 const entry = map.at(map_index);
9243 const index = entry.acquire().unwrap() orelse return null;
9244 if (entry.hash != hash) continue;
9245 if (index.eqlSlice(key, ip)) return index;
9246 }
91239247}
91249248
91259249pub fn typeOf(ip: *const InternPool, index: Index) Index {