authorgravatar for jacobly@ziglang.orgJacob Young <jacobly@ziglang.org> 2024-07-03 22:37:09-04:00
committergravatar for jacobly@ziglang.orgJacob Young <jacobly@ziglang.org> 2024-07-07 22:59:52-04:00
log8293ff94cf2798a2678b91019979472d34273bdb
tree8ec93b2e00f8bfd0e7fc477b09180dc910230fa3
parent3e1b190fe6955ba051d961494433b8346af2af38

InternPool: implement and use thread-safe list for strings


6 files changed, 380 insertions(+), 157 deletions(-)

lib/std/multi_array_list.zig+1-1
......@@ -534,7 +534,7 @@ pub fn MultiArrayList(comptime T: type) type {
534534 self.sortInternal(a, b, ctx, .unstable);
535535 }
536536
537 fn capacityInBytes(capacity: usize) usize {
537 pub fn capacityInBytes(capacity: usize) usize {
538538 comptime var elem_bytes: usize = 0;
539539 inline for (sizes.bytes) |size| elem_bytes += size;
540540 return elem_bytes * capacity;
src/Compilation.zig+3-3
......@@ -2748,7 +2748,7 @@ const Header = extern struct {
27482748 items_len: u32,
27492749 extra_len: u32,
27502750 limbs_len: u32,
2751 string_bytes_len: u32,
2751 //string_bytes_len: u32,
27522752 tracked_insts_len: u32,
27532753 src_hash_deps_len: u32,
27542754 decl_val_deps_len: u32,
......@@ -2777,7 +2777,7 @@ pub fn saveState(comp: *Compilation) !void {
27772777 .items_len = @intCast(ip.items.len),
27782778 .extra_len = @intCast(ip.extra.items.len),
27792779 .limbs_len = @intCast(ip.limbs.items.len),
2780 .string_bytes_len = @intCast(ip.string_bytes.items.len),
2780 //.string_bytes_len = @intCast(ip.string_bytes.items.len),
27812781 .tracked_insts_len = @intCast(ip.tracked_insts.count()),
27822782 .src_hash_deps_len = @intCast(ip.src_hash_deps.count()),
27832783 .decl_val_deps_len = @intCast(ip.decl_val_deps.count()),
......@@ -2794,7 +2794,7 @@ pub fn saveState(comp: *Compilation) !void {
27942794 addBuf(&bufs_list, &bufs_len, mem.sliceAsBytes(ip.extra.items));
27952795 addBuf(&bufs_list, &bufs_len, mem.sliceAsBytes(ip.items.items(.data)));
27962796 addBuf(&bufs_list, &bufs_len, mem.sliceAsBytes(ip.items.items(.tag)));
2797 addBuf(&bufs_list, &bufs_len, ip.string_bytes.items);
2797 //addBuf(&bufs_list, &bufs_len, ip.string_bytes.items);
27982798 addBuf(&bufs_list, &bufs_len, mem.sliceAsBytes(ip.tracked_insts.keys()));
27992799
28002800 addBuf(&bufs_list, &bufs_len, mem.sliceAsBytes(ip.src_hash_deps.keys()));
src/InternPool.zig+335-125
......@@ -2,9 +2,11 @@
22//! This data structure is self-contained, with the following exceptions:
33//! * Module.Namespace has a pointer to Module.File
44
5local: []Local = &.{},
6shard_shift: std.math.Log2Int(usize) = 0,
5locals: []Local = &.{},
76shards: []Shard = &.{},
7tid_width: std.math.Log2Int(u32) = 0,
8tid_shift_31: std.math.Log2Int(u32) = 31,
9tid_shift_32: std.math.Log2Int(u32) = 31,
810
911items: std.MultiArrayList(Item) = .{},
1012extra: std.ArrayListUnmanaged(u32) = .{},
......@@ -13,12 +15,6 @@ extra: std.ArrayListUnmanaged(u32) = .{},
1315/// Use the helper methods instead of accessing this directly in order to not
1416/// violate the above mechanism.
1517limbs: std.ArrayListUnmanaged(u64) = .{},
16/// In order to store references to strings in fewer bytes, we copy all
17/// string bytes into here. String bytes can be null. It is up to whomever
18/// is referencing the data here whether they want to store both index and length,
19/// thus allowing null bytes, or store only index, and use null-termination. The
20/// `string_bytes` array is agnostic to either usage.
21string_bytes: std.ArrayListUnmanaged(u8) = .{},
2218
2319/// Rather than allocating Decl objects with an Allocator, we instead allocate
2420/// them with this SegmentedList. This provides four advantages:
......@@ -345,52 +341,237 @@ pub const DepEntry = extern struct {
345341};
346342
347343const Local = struct {
348 aligned: void align(std.atomic.cache_line) = {},
344 shared: Shared align(std.atomic.cache_line),
345 mutate: struct {
346 arena: std.heap.ArenaAllocator.State,
347 strings: Mutate,
348 } align(std.atomic.cache_line),
349349
350 /// header: List.Header,
351 /// data: [capacity]u32,
352 /// tag: [header.capacity]Tag,
353 items: List,
350 const Shared = struct {
351 strings: Strings,
352 };
354353
355 /// header: List.Header,
356 /// extra: [header.capacity]u32,
357 extra: List,
354 const Strings = List(struct { u8 });
358355
359 /// header: List.Header,
360 /// bytes: [header.capacity]u8,
361 strings: List,
356 const Mutate = struct {
357 len: u32,
362358
363 arena: std.heap.ArenaAllocator.State,
359 const empty: Mutate = .{
360 .len = 0,
361 };
362 };
364363
365 const List = struct {
366 entries: [*]u32,
364 fn List(comptime Elem: type) type {
365 assert(@typeInfo(Elem) == .Struct);
366 return struct {
367 bytes: [*]align(@alignOf(Elem)) u8,
368
369 const ListSelf = @This();
370 const Mutable = struct {
371 gpa: std.mem.Allocator,
372 arena: *std.heap.ArenaAllocator.State,
373 mutate: *Mutate,
374 list: *ListSelf,
375
376 const fields = std.enums.values(std.meta.FieldEnum(Elem));
377
378 fn Slice(comptime opts: struct { is_const: bool = false }) type {
379 const elem_info = @typeInfo(Elem).Struct;
380 const elem_fields = elem_info.fields;
381 var new_fields: [elem_fields.len]std.builtin.Type.StructField = undefined;
382 for (&new_fields, elem_fields) |*new_field, elem_field| new_field.* = .{
383 .name = elem_field.name,
384 .type = @Type(.{ .Pointer = .{
385 .size = .Slice,
386 .is_const = opts.is_const,
387 .is_volatile = false,
388 .alignment = 0,
389 .address_space = .generic,
390 .child = elem_field.type,
391 .is_allowzero = false,
392 .sentinel = null,
393 } }),
394 .default_value = null,
395 .is_comptime = false,
396 .alignment = 0,
397 };
398 return @Type(.{ .Struct = .{
399 .layout = .auto,
400 .fields = &new_fields,
401 .decls = &.{},
402 .is_tuple = elem_info.is_tuple,
403 } });
404 }
367405
368 const empty: List = .{ .entries = @constCast(&(extern struct {
369 header: Header,
370 entries: [0]u32,
371 }{
372 .header = .{ .len = 0, .capacity = 0 },
373 .entries = .{},
374 }).entries) };
406 pub fn appendAssumeCapacity(mutable: Mutable, elem: Elem) void {
407 var mutable_view = mutable.view();
408 defer mutable.lenPtr().* = @intCast(mutable_view.len);
409 mutable_view.appendAssumeCapacity(elem);
410 }
375411
376 fn acquire(list: *const List) List {
377 return .{ .entries = @atomicLoad([*]u32, &list.entries, .acquire) };
378 }
379 fn release(list: *List, new_list: List) void {
380 @atomicStore([*]u32, &list.entries, new_list.entries, .release);
381 }
412 pub fn appendSliceAssumeCapacity(
413 mutable: Mutable,
414 slice: Slice(.{ .is_const = true }),
415 ) void {
416 if (fields.len == 0) return;
417 const mutable_len = mutable.lenPtr();
418 const start = mutable_len.*;
419 const slice_len = @field(slice, @tagName(fields[0])).len;
420 assert(slice_len < mutable.capacityPtr().* - start);
421 mutable_len.* = @intCast(start + slice_len);
422 const mutable_view = mutable.view();
423 inline for (fields) |field| {
424 const field_slice = @field(slice, @tagName(field));
425 assert(field_slice.len == slice_len);
426 @memcpy(mutable_view.items(field)[start..][0..slice_len], field_slice);
427 }
428 }
382429
383 const Header = extern struct {
384 len: u32,
385 capacity: u32,
430 pub fn appendNTimes(mutable: Mutable, elem: Elem, len: usize) Allocator.Error!void {
431 try mutable.ensureUnusedCapacity(len);
432 mutable.appendNTimesAssumeCapacity(elem, len);
433 }
434
435 pub fn appendNTimesAssumeCapacity(mutable: Mutable, elem: Elem, len: usize) void {
436 const mutable_len = mutable.lenPtr();
437 const start = mutable_len.*;
438 assert(len <= mutable.capacityPtr().* - start);
439 mutable_len.* = @intCast(start + len);
440 const mutable_view = mutable.view();
441 inline for (fields) |field| {
442 @memset(mutable_view.items(field)[start..][0..len], @field(elem, @tagName(field)));
443 }
444 }
445
446 pub fn addManyAsSlice(mutable: Mutable, len: usize) Allocator.Error!Slice(.{}) {
447 try mutable.ensureUnusedCapacity(len);
448 return mutable.addManyAsSliceAssumeCapacity(len);
449 }
450
451 pub fn addManyAsSliceAssumeCapacity(mutable: Mutable, len: usize) Slice(.{}) {
452 const mutable_len = mutable.lenPtr();
453 const start = mutable_len.*;
454 assert(len <= mutable.capacityPtr().* - start);
455 mutable_len.* = @intCast(start + len);
456 const mutable_view = mutable.view();
457 var slice: Slice(.{}) = undefined;
458 inline for (fields) |field| {
459 @field(slice, @tagName(field)) = mutable_view.items(field)[start..][0..len];
460 }
461 return slice;
462 }
463
464 pub fn shrinkRetainingCapacity(mutable: Mutable, len: usize) void {
465 const mutable_len = mutable.lenPtr();
466 assert(len <= mutable_len.*);
467 mutable_len.* = @intCast(len);
468 }
469
470 pub fn ensureUnusedCapacity(mutable: Mutable, unused_capacity: usize) Allocator.Error!void {
471 try mutable.ensureTotalCapacity(@intCast(mutable.lenPtr().* + unused_capacity));
472 }
473
474 pub fn ensureTotalCapacity(mutable: Mutable, total_capacity: usize) Allocator.Error!void {
475 const old_capacity = mutable.capacityPtr().*;
476 if (old_capacity >= total_capacity) return;
477 var new_capacity = old_capacity;
478 while (new_capacity < total_capacity) new_capacity = (new_capacity + 10) * 2;
479 try mutable.setCapacity(new_capacity);
480 }
481
482 fn setCapacity(mutable: Mutable, capacity: u32) Allocator.Error!void {
483 var arena = mutable.arena.promote(mutable.gpa);
484 defer mutable.arena.* = arena.state;
485 const buf = try arena.allocator().alignedAlloc(
486 u8,
487 alignment,
488 bytes_offset + View.capacityInBytes(capacity),
489 );
490 var new_list: ListSelf = .{ .bytes = @ptrCast(buf[bytes_offset..].ptr) };
491 new_list.header().* = .{ .capacity = capacity };
492 const len = mutable.lenPtr().*;
493 const old_slice = mutable.list.view().slice();
494 const new_slice = new_list.view().slice();
495 inline for (fields) |field| {
496 @memcpy(new_slice.items(field)[0..len], old_slice.items(field)[0..len]);
497 }
498 mutable.list.release(new_list);
499 }
500
501 fn view(mutable: Mutable) View {
502 return .{
503 .bytes = mutable.list.bytes,
504 .len = mutable.lenPtr().*,
505 .capacity = mutable.capacityPtr().*,
506 };
507 }
508
509 pub fn lenPtr(mutable: Mutable) *u32 {
510 return &mutable.mutate.len;
511 }
512
513 pub fn capacityPtr(mutable: Mutable) *u32 {
514 return &mutable.list.header().capacity;
515 }
516 };
517
518 const empty: ListSelf = .{ .bytes = @constCast(&(extern struct {
519 header: Header,
520 bytes: [0]u8,
521 }{
522 .header = .{ .capacity = 0 },
523 .bytes = .{},
524 }).bytes) };
386525
387 const fields_len = @typeInfo(Header).Struct.fields.len;
526 const alignment = @max(@alignOf(Header), @alignOf(Elem));
527 const bytes_offset = std.mem.alignForward(usize, @sizeOf(Header), @alignOf(Elem));
528 const View = std.MultiArrayList(Elem);
529
530 fn acquire(list: *const ListSelf) ListSelf {
531 return .{ .bytes = @atomicLoad([*]align(@alignOf(Elem)) u8, &list.bytes, .acquire) };
532 }
533 fn release(list: *ListSelf, new_list: ListSelf) void {
534 @atomicStore([*]align(@alignOf(Elem)) u8, &list.bytes, new_list.bytes, .release);
535 }
536
537 const Header = extern struct {
538 capacity: u32,
539 };
540 fn header(list: ListSelf) *Header {
541 return @ptrFromInt(@intFromPtr(list.bytes) - bytes_offset);
542 }
543
544 fn view(list: ListSelf) View {
545 const capacity = list.header().capacity;
546 return .{
547 .bytes = list.bytes,
548 .len = capacity,
549 .capacity = capacity,
550 };
551 }
388552 };
389 fn header(list: List) *Header {
390 return @ptrCast(list.entries - Header.fields_len);
391 }
392 };
553 }
554
555 /// In order to store references to strings in fewer bytes, we copy all
556 /// string bytes into here. String bytes can be null. It is up to whomever
557 /// is referencing the data here whether they want to store both index and length,
558 /// thus allowing null bytes, or store only index, and use null-termination. The
559 /// `strings` array is agnostic to either usage.
560 pub fn getMutableStrings(local: *Local, gpa: std.mem.Allocator) Strings.Mutable {
561 return .{
562 .gpa = gpa,
563 .arena = &local.mutate.arena,
564 .mutate = &local.mutate.strings,
565 .list = &local.shared.strings,
566 };
567 }
393568};
569pub fn getLocal(ip: *InternPool, tid: Zcu.PerThread.Id) *Local {
570 return &ip.locals[@intFromEnum(tid)];
571}
572pub fn getLocalShared(ip: *const InternPool, tid: Zcu.PerThread.Id) *const Local.Shared {
573 return &ip.locals[@intFromEnum(tid)].shared;
574}
394575
395576const Shard = struct {
396577 shared: struct {
......@@ -448,7 +629,7 @@ const Shard = struct {
448629 }
449630 };
450631 fn header(map: @This()) *Header {
451 return &(@as([*]Header, @ptrCast(map.entries)) - 1)[0];
632 return @ptrFromInt(@intFromPtr(map.entries) - entries_offset);
452633 }
453634
454635 const Entry = extern struct {
......@@ -465,6 +646,17 @@ const Shard = struct {
465646 };
466647 }
467648};
649fn getShard(ip: *InternPool, tid: Zcu.PerThread.Id) *Shard {
650 return &ip.shards[@intFromEnum(tid)];
651}
652
653fn getTidMask(ip: *const InternPool) u32 {
654 assert(std.math.isPowerOfTwo(ip.shards.len));
655 return @intCast(ip.shards.len - 1);
656}
657fn getIndexMask(ip: *const InternPool, comptime BackingInt: type) u32 {
658 return @as(u32, std.math.maxInt(BackingInt)) >> ip.tid_width;
659}
468660
469661const FieldMap = std.ArrayHashMapUnmanaged(void, void, std.array_hash_map.AutoContext(void), false);
470662
......@@ -560,18 +752,18 @@ pub const OptionalNamespaceIndex = enum(u32) {
560752 }
561753};
562754
563/// An index into `string_bytes`.
755/// An index into `strings`.
564756pub const String = enum(u32) {
565757 /// An empty string.
566758 empty = 0,
567759 _,
568760
569761 pub fn toSlice(string: String, len: u64, ip: *const InternPool) []const u8 {
570 return ip.string_bytes.items[@intFromEnum(string)..][0..@intCast(len)];
762 return string.toOverlongSlice(ip)[0..@intCast(len)];
571763 }
572764
573765 pub fn at(string: String, index: u64, ip: *const InternPool) u8 {
574 return ip.string_bytes.items[@intCast(@intFromEnum(string) + index)];
766 return string.toOverlongSlice(ip)[@intCast(index)];
575767 }
576768
577769 pub fn toNullTerminatedString(string: String, len: u64, ip: *const InternPool) NullTerminatedString {
......@@ -579,9 +771,32 @@ pub const String = enum(u32) {
579771 assert(string.at(len, ip) == 0);
580772 return @enumFromInt(@intFromEnum(string));
581773 }
774
775 const Unwrapped = struct {
776 tid: Zcu.PerThread.Id,
777 index: u32,
778
779 fn wrap(unwrapped: Unwrapped, ip: *const InternPool) String {
780 assert(@intFromEnum(unwrapped.tid) <= ip.getTidMask());
781 assert(unwrapped.index <= ip.getIndexMask(u32));
782 return @enumFromInt(@intFromEnum(unwrapped.tid) << ip.tid_shift_32 | unwrapped.index);
783 }
784 };
785 fn unwrap(string: String, ip: *const InternPool) Unwrapped {
786 return .{
787 .tid = @enumFromInt(@intFromEnum(string) >> ip.tid_shift_32 & ip.getTidMask()),
788 .index = @intFromEnum(string) & ip.getIndexMask(u32),
789 };
790 }
791
792 fn toOverlongSlice(string: String, ip: *const InternPool) []const u8 {
793 const unwrapped = string.unwrap(ip);
794 const strings = ip.getLocalShared(unwrapped.tid).strings.acquire();
795 return strings.view().items(.@"0")[unwrapped.index..];
796 }
582797};
583798
584/// An index into `string_bytes` which might be `none`.
799/// An index into `strings` which might be `none`.
585800pub const OptionalString = enum(u32) {
586801 /// This is distinct from `none` - it is a valid index that represents empty string.
587802 empty = 0,
......@@ -597,7 +812,7 @@ pub const OptionalString = enum(u32) {
597812 }
598813};
599814
600/// An index into `string_bytes`.
815/// An index into `strings`.
601816pub const NullTerminatedString = enum(u32) {
602817 /// An empty string.
603818 empty = 0,
......@@ -623,12 +838,8 @@ pub const NullTerminatedString = enum(u32) {
623838 return @enumFromInt(@intFromEnum(self));
624839 }
625840
626 fn toOverlongSlice(string: NullTerminatedString, ip: *const InternPool) []const u8 {
627 return ip.string_bytes.items[@intFromEnum(string)..];
628 }
629
630841 pub fn toSlice(string: NullTerminatedString, ip: *const InternPool) [:0]const u8 {
631 const overlong_slice = string.toOverlongSlice(ip);
842 const overlong_slice = string.toString().toOverlongSlice(ip);
632843 return overlong_slice[0..std.mem.indexOfScalar(u8, overlong_slice, 0).? :0];
633844 }
634845
......@@ -637,7 +848,7 @@ pub const NullTerminatedString = enum(u32) {
637848 }
638849
639850 pub fn eqlSlice(string: NullTerminatedString, slice: []const u8, ip: *const InternPool) bool {
640 const overlong_slice = string.toOverlongSlice(ip);
851 const overlong_slice = string.toString().toOverlongSlice(ip);
641852 return overlong_slice.len > slice.len and
642853 std.mem.eql(u8, overlong_slice[0..slice.len], slice) and
643854 overlong_slice[slice.len] == 0;
......@@ -688,12 +899,12 @@ pub const NullTerminatedString = enum(u32) {
688899 } else @compileError("invalid format string '" ++ specifier ++ "' for '" ++ @typeName(NullTerminatedString) ++ "'");
689900 }
690901
691 pub fn fmt(self: NullTerminatedString, ip: *const InternPool) std.fmt.Formatter(format) {
692 return .{ .data = .{ .string = self, .ip = ip } };
902 pub fn fmt(string: NullTerminatedString, ip: *const InternPool) std.fmt.Formatter(format) {
903 return .{ .data = .{ .string = string, .ip = ip } };
693904 }
694905};
695906
696/// An index into `string_bytes` which might be `none`.
907/// An index into `strings` which might be `none`.
697908pub const OptionalNullTerminatedString = enum(u32) {
698909 /// This is distinct from `none` - it is a valid index that represents empty string.
699910 empty = 0,
......@@ -4077,7 +4288,7 @@ pub const FuncAnalysis = packed struct(u32) {
40774288pub const Bytes = struct {
40784289 /// The type of the aggregate
40794290 ty: Index,
4080 /// Index into string_bytes, of len ip.aggregateTypeLen(ty)
4291 /// Index into strings, of len ip.aggregateTypeLen(ty)
40814292 bytes: String,
40824293};
40834294
......@@ -4647,16 +4858,21 @@ pub fn init(ip: *InternPool, gpa: Allocator, total_threads: usize) !void {
46474858 errdefer ip.deinit(gpa);
46484859 assert(ip.items.len == 0);
46494860
4650 ip.local = try gpa.alloc(Local, total_threads);
4651 @memset(ip.local, .{
4652 .items = Local.List.empty,
4653 .extra = Local.List.empty,
4654 .strings = Local.List.empty,
4655 .arena = .{},
4861 ip.locals = try gpa.alloc(Local, total_threads);
4862 @memset(ip.locals, .{
4863 .shared = .{
4864 .strings = Local.Strings.empty,
4865 },
4866 .mutate = .{
4867 .arena = .{},
4868 .strings = Local.Mutate.empty,
4869 },
46564870 });
46574871
4658 ip.shard_shift = @intCast(std.math.log2_int_ceil(usize, total_threads));
4659 ip.shards = try gpa.alloc(Shard, @as(usize, 1) << ip.shard_shift);
4872 ip.tid_width = @intCast(std.math.log2_int_ceil(usize, total_threads));
4873 ip.tid_shift_31 = 31 - ip.tid_width;
4874 ip.tid_shift_32 = ip.tid_shift_31 +| 1;
4875 ip.shards = try gpa.alloc(Shard, @as(usize, 1) << ip.tid_width);
46604876 @memset(ip.shards, .{
46614877 .shared = .{
46624878 .map = Shard.Map(Index).empty,
......@@ -4705,7 +4921,6 @@ pub fn deinit(ip: *InternPool, gpa: Allocator) void {
47054921 ip.items.deinit(gpa);
47064922 ip.extra.deinit(gpa);
47074923 ip.limbs.deinit(gpa);
4708 ip.string_bytes.deinit(gpa);
47094924
47104925 ip.decls_free_list.deinit(gpa);
47114926 ip.allocated_decls.deinit(gpa);
......@@ -4732,8 +4947,8 @@ pub fn deinit(ip: *InternPool, gpa: Allocator) void {
47324947 ip.files.deinit(gpa);
47334948
47344949 gpa.free(ip.shards);
4735 for (ip.local) |*local| local.arena.promote(gpa).deinit();
4736 gpa.free(ip.local);
4950 for (ip.locals) |*local| local.mutate.arena.promote(gpa).deinit();
4951 gpa.free(ip.locals);
47374952
47384953 ip.* = undefined;
47394954}
......@@ -5437,8 +5652,9 @@ fn getOrPutKey(
54375652 }
54385653 const map_header = map.header().*;
54395654 if (shard.mutate.map.len >= map_header.capacity * 3 / 5) {
5440 var arena = ip.local[@intFromEnum(tid)].arena.promote(gpa);
5441 defer ip.local[@intFromEnum(tid)].arena = arena.state;
5655 const arena_state = &ip.getLocal(tid).mutate.arena;
5656 var arena = arena_state.promote(gpa);
5657 defer arena_state.* = arena.state;
54425658 const new_map_capacity = map_header.capacity * 2;
54435659 const new_map_buf = try arena.allocator().alignedAlloc(
54445660 u8,
......@@ -6194,33 +6410,32 @@ pub fn get(ip: *InternPool, gpa: Allocator, tid: Zcu.PerThread.Id, key: Key) All
61946410 }
61956411
61966412 if (child == .u8_type) bytes: {
6197 const string_bytes_index = ip.string_bytes.items.len;
6198 try ip.string_bytes.ensureUnusedCapacity(gpa, @intCast(len_including_sentinel + 1));
6413 const strings = ip.getLocal(tid).getMutableStrings(gpa);
6414 const start = strings.lenPtr().*;
6415 try strings.ensureUnusedCapacity(@intCast(len_including_sentinel + 1));
61996416 try ip.extra.ensureUnusedCapacity(gpa, @typeInfo(Bytes).Struct.fields.len);
62006417 switch (aggregate.storage) {
6201 .bytes => |bytes| ip.string_bytes.appendSliceAssumeCapacity(bytes.toSlice(len, ip)),
6418 .bytes => |bytes| strings.appendSliceAssumeCapacity(.{bytes.toSlice(len, ip)}),
62026419 .elems => |elems| for (elems[0..@intCast(len)]) |elem| switch (ip.indexToKey(elem)) {
62036420 .undef => {
6204 ip.string_bytes.shrinkRetainingCapacity(string_bytes_index);
6421 strings.shrinkRetainingCapacity(start);
62056422 break :bytes;
62066423 },
6207 .int => |int| ip.string_bytes.appendAssumeCapacity(
6208 @intCast(int.storage.u64),
6209 ),
6424 .int => |int| strings.appendAssumeCapacity(.{@intCast(int.storage.u64)}),
62106425 else => unreachable,
62116426 },
62126427 .repeated_elem => |elem| switch (ip.indexToKey(elem)) {
62136428 .undef => break :bytes,
62146429 .int => |int| @memset(
6215 ip.string_bytes.addManyAsSliceAssumeCapacity(@intCast(len)),
6430 strings.addManyAsSliceAssumeCapacity(@intCast(len))[0],
62166431 @intCast(int.storage.u64),
62176432 ),
62186433 else => unreachable,
62196434 },
62206435 }
6221 if (sentinel != .none) ip.string_bytes.appendAssumeCapacity(
6436 if (sentinel != .none) strings.appendAssumeCapacity(.{
62226437 @intCast(ip.indexToKey(sentinel).int.storage.u64),
6223 );
6438 });
62246439 const string = try ip.getOrPutTrailingString(
62256440 gpa,
62266441 tid,
......@@ -9050,10 +9265,11 @@ pub fn getOrPutString(
90509265 slice: []const u8,
90519266 comptime embedded_nulls: EmbeddedNulls,
90529267) Allocator.Error!embedded_nulls.StringType() {
9053 try ip.string_bytes.ensureUnusedCapacity(gpa, slice.len + 1);
9054 ip.string_bytes.appendSliceAssumeCapacity(slice);
9055 ip.string_bytes.appendAssumeCapacity(0);
9056 return ip.getOrPutTrailingString(gpa, tid, slice.len + 1, embedded_nulls);
9268 const strings = ip.getLocal(tid).getMutableStrings(gpa);
9269 try strings.ensureUnusedCapacity(slice.len + 1);
9270 strings.appendSliceAssumeCapacity(.{slice});
9271 strings.appendAssumeCapacity(.{0});
9272 return ip.getOrPutTrailingString(gpa, tid, @intCast(slice.len + 1), embedded_nulls);
90579273}
90589274
90599275pub fn getOrPutStringFmt(
......@@ -9064,11 +9280,12 @@ pub fn getOrPutStringFmt(
90649280 args: anytype,
90659281 comptime embedded_nulls: EmbeddedNulls,
90669282) Allocator.Error!embedded_nulls.StringType() {
9067 // ensure that references to string_bytes in args do not get invalidated
9068 const len: usize = @intCast(std.fmt.count(format, args) + 1);
9069 try ip.string_bytes.ensureUnusedCapacity(gpa, len);
9070 ip.string_bytes.writer(undefined).print(format, args) catch unreachable;
9071 ip.string_bytes.appendAssumeCapacity(0);
9283 // ensure that references to strings in args do not get invalidated
9284 const format_z = format ++ .{0};
9285 const len: u32 = @intCast(std.fmt.count(format_z, args));
9286 const strings = ip.getLocal(tid).getMutableStrings(gpa);
9287 const slice = try strings.addManyAsSlice(len);
9288 assert((std.fmt.bufPrint(slice[0], format_z, args) catch unreachable).len == len);
90729289 return ip.getOrPutTrailingString(gpa, tid, len, embedded_nulls);
90739290}
90749291
......@@ -9083,47 +9300,33 @@ pub fn getOrPutStringOpt(
90839300 return string.toOptional();
90849301}
90859302
9086/// Uses the last len bytes of ip.string_bytes as the key.
9303/// Uses the last len bytes of strings as the key.
90879304pub fn getOrPutTrailingString(
90889305 ip: *InternPool,
90899306 gpa: Allocator,
90909307 tid: Zcu.PerThread.Id,
9091 len: usize,
9308 len: u32,
90929309 comptime embedded_nulls: EmbeddedNulls,
90939310) Allocator.Error!embedded_nulls.StringType() {
9094 const string_bytes = &ip.string_bytes;
9095 const str_index: u32 = @intCast(string_bytes.items.len - len);
9096 if (len > 0 and string_bytes.getLast() == 0) {
9097 _ = string_bytes.pop();
9311 const strings = ip.getLocal(tid).getMutableStrings(gpa);
9312 const start: u32 = @intCast(strings.lenPtr().* - len);
9313 if (len > 0 and strings.view().items(.@"0")[strings.lenPtr().* - 1] == 0) {
9314 strings.lenPtr().* -= 1;
90989315 } else {
9099 try string_bytes.ensureUnusedCapacity(gpa, 1);
9316 try strings.ensureUnusedCapacity(1);
91009317 }
9101 const key: []const u8 = string_bytes.items[str_index..];
9318 const key: []const u8 = strings.view().items(.@"0")[start..];
9319 const value: embedded_nulls.StringType() =
9320 @enumFromInt(@intFromEnum(tid) << ip.tid_shift_32 | start);
91029321 const has_embedded_null = std.mem.indexOfScalar(u8, key, 0) != null;
91039322 switch (embedded_nulls) {
91049323 .no_embedded_nulls => assert(!has_embedded_null),
91059324 .maybe_embedded_nulls => if (has_embedded_null) {
9106 string_bytes.appendAssumeCapacity(0);
9107 return @enumFromInt(str_index);
9325 strings.appendAssumeCapacity(.{0});
9326 return value;
91089327 },
91099328 }
9110 const maybe_existing_index = try ip.getOrPutStringValue(gpa, tid, key, @enumFromInt(str_index));
9111 if (maybe_existing_index.unwrap()) |existing_index| {
9112 string_bytes.shrinkRetainingCapacity(str_index);
9113 return @enumFromInt(@intFromEnum(existing_index));
9114 } else {
9115 string_bytes.appendAssumeCapacity(0);
9116 return @enumFromInt(str_index);
9117 }
9118}
91199329
9120fn getOrPutStringValue(
9121 ip: *InternPool,
9122 gpa: Allocator,
9123 tid: Zcu.PerThread.Id,
9124 key: []const u8,
9125 value: NullTerminatedString,
9126) Allocator.Error!OptionalNullTerminatedString {
91279330 const full_hash = Hash.hash(0, key);
91289331 const hash: u32 = @truncate(full_hash >> 32);
91299332 const shard = &ip.shards[@intCast(full_hash & (ip.shards.len - 1))];
......@@ -9136,7 +9339,9 @@ fn getOrPutStringValue(
91369339 const entry = &map.entries[map_index];
91379340 const index = entry.acquire().unwrap() orelse break;
91389341 if (entry.hash != hash) continue;
9139 if (index.eqlSlice(key, ip)) return index.toOptional();
9342 if (!index.eqlSlice(key, ip)) continue;
9343 strings.shrinkRetainingCapacity(start);
9344 return @enumFromInt(@intFromEnum(index));
91409345 }
91419346 shard.mutate.string_map.mutex.lock();
91429347 defer shard.mutate.string_map.mutex.unlock();
......@@ -9151,18 +9356,22 @@ fn getOrPutStringValue(
91519356 const entry = &map.entries[map_index];
91529357 const index = entry.acquire().unwrap() orelse break;
91539358 if (entry.hash != hash) continue;
9154 if (index.eqlSlice(key, ip)) return index.toOptional();
9359 if (!index.eqlSlice(key, ip)) continue;
9360 strings.shrinkRetainingCapacity(start);
9361 return @enumFromInt(@intFromEnum(index));
91559362 }
91569363 defer shard.mutate.string_map.len += 1;
91579364 const map_header = map.header().*;
91589365 if (shard.mutate.string_map.len < map_header.capacity * 3 / 5) {
91599366 const entry = &map.entries[map_index];
91609367 entry.hash = hash;
9161 entry.release(value.toOptional());
9162 return .none;
9368 entry.release(@enumFromInt(@intFromEnum(value)));
9369 strings.appendAssumeCapacity(.{0});
9370 return value;
91639371 }
9164 var arena = ip.local[@intFromEnum(tid)].arena.promote(gpa);
9165 defer ip.local[@intFromEnum(tid)].arena = arena.state;
9372 const arena_state = &ip.getLocal(tid).mutate.arena;
9373 var arena = arena_state.promote(gpa);
9374 defer arena_state.* = arena.state;
91669375 const new_map_capacity = map_header.capacity * 2;
91679376 const new_map_buf = try arena.allocator().alignedAlloc(
91689377 u8,
......@@ -9197,11 +9406,12 @@ fn getOrPutStringValue(
91979406 if (map.entries[map_index].value == .none) break;
91989407 }
91999408 map.entries[map_index] = .{
9200 .value = value.toOptional(),
9409 .value = @enumFromInt(@intFromEnum(value)),
92019410 .hash = hash,
92029411 };
92039412 shard.shared.string_map.release(new_map);
9204 return .none;
9413 strings.appendAssumeCapacity(.{0});
9414 return value;
92059415}
92069416
92079417pub fn getString(ip: *InternPool, key: []const u8) OptionalNullTerminatedString {
src/Value.zig+10-7
......@@ -65,8 +65,9 @@ pub fn toIpString(val: Value, ty: Type, pt: Zcu.PerThread) !InternPool.NullTermi
6565 .elems => return arrayToIpString(val, ty.arrayLen(mod), pt),
6666 .repeated_elem => |elem| {
6767 const byte: u8 = @intCast(Value.fromInterned(elem).toUnsignedInt(pt));
68 const len: usize = @intCast(ty.arrayLen(mod));
69 try ip.string_bytes.appendNTimes(mod.gpa, byte, len);
68 const len: u32 = @intCast(ty.arrayLen(mod));
69 const strings = ip.getLocal(pt.tid).getMutableStrings(mod.gpa);
70 try strings.appendNTimes(.{byte}, len);
7071 return ip.getOrPutTrailingString(mod.gpa, pt.tid, len, .no_embedded_nulls);
7172 },
7273 }
......@@ -107,16 +108,18 @@ fn arrayToIpString(val: Value, len_u64: u64, pt: Zcu.PerThread) !InternPool.Null
107108 const mod = pt.zcu;
108109 const gpa = mod.gpa;
109110 const ip = &mod.intern_pool;
110 const len: usize = @intCast(len_u64);
111 try ip.string_bytes.ensureUnusedCapacity(gpa, len);
111 const len: u32 = @intCast(len_u64);
112 const strings = ip.getLocal(pt.tid).getMutableStrings(gpa);
113 const strings_len = strings.lenPtr();
114 try strings.ensureUnusedCapacity(len);
112115 for (0..len) |i| {
113116 // I don't think elemValue has the possibility to affect ip.string_bytes. Let's
114117 // assert just to be sure.
115 const prev = ip.string_bytes.items.len;
118 const prev_len = strings_len.*;
116119 const elem_val = try val.elemValue(pt, i);
117 assert(ip.string_bytes.items.len == prev);
120 assert(strings_len.* == prev_len);
118121 const byte: u8 = @intCast(elem_val.toUnsignedInt(pt));
119 ip.string_bytes.appendAssumeCapacity(byte);
122 strings.appendAssumeCapacity(.{byte});
120123 }
121124 return ip.getOrPutTrailingString(gpa, pt.tid, len, .no_embedded_nulls);
122125}
src/Zcu.zig+25-16
......@@ -693,38 +693,39 @@ pub const Namespace = struct {
693693 ) !InternPool.NullTerminatedString {
694694 const zcu = pt.zcu;
695695 const ip = &zcu.intern_pool;
696 const count = count: {
696
697 const gpa = zcu.gpa;
698 const strings = ip.getLocal(pt.tid).getMutableStrings(gpa);
699 // Protects reads of interned strings from being reallocated during the call to
700 // renderFullyQualifiedName.
701 const slice = try strings.addManyAsSlice(count: {
697702 var count: usize = name.length(ip) + 1;
698703 var cur_ns = &ns;
699704 while (true) {
700705 const decl = zcu.declPtr(cur_ns.decl_index);
701 count += decl.name.length(ip) + 1;
702706 cur_ns = zcu.namespacePtr(cur_ns.parent.unwrap() orelse {
703 count += ns.fileScope(zcu).sub_file_path.len;
707 count += ns.fileScope(zcu).fullyQualifiedNameLen();
704708 break :count count;
705709 });
710 count += decl.name.length(ip) + 1;
706711 }
707 };
708
709 const gpa = zcu.gpa;
710 const start = ip.string_bytes.items.len;
711 // Protects reads of interned strings from being reallocated during the call to
712 // renderFullyQualifiedName.
713 try ip.string_bytes.ensureUnusedCapacity(gpa, count);
714 ns.renderFullyQualifiedName(zcu, name, ip.string_bytes.writer(gpa)) catch unreachable;
712 });
713 var fbs = std.io.fixedBufferStream(slice[0]);
714 ns.renderFullyQualifiedName(zcu, name, fbs.writer()) catch unreachable;
715 assert(fbs.pos == slice[0].len);
715716
716717 // Sanitize the name for nvptx which is more restrictive.
717718 // TODO This should be handled by the backend, not the frontend. Have a
718719 // look at how the C backend does it for inspiration.
719720 const cpu_arch = zcu.root_mod.resolved_target.result.cpu.arch;
720721 if (cpu_arch.isNvptx()) {
721 for (ip.string_bytes.items[start..]) |*byte| switch (byte.*) {
722 for (slice[0]) |*byte| switch (byte.*) {
722723 '{', '}', '*', '[', ']', '(', ')', ',', ' ', '\'' => byte.* = '_',
723724 else => {},
724725 };
725726 }
726727
727 return ip.getOrPutTrailingString(gpa, pt.tid, ip.string_bytes.items.len - start, .no_embedded_nulls);
728 return ip.getOrPutTrailingString(gpa, pt.tid, @intCast(slice[0].len), .no_embedded_nulls);
728729 }
729730
730731 pub fn getType(ns: Namespace, zcu: *Zcu) Type {
......@@ -859,6 +860,11 @@ pub const File = struct {
859860 return &file.tree;
860861 }
861862
863 pub fn fullyQualifiedNameLen(file: File) usize {
864 const ext = std.fs.path.extension(file.sub_file_path);
865 return file.sub_file_path.len - ext.len;
866 }
867
862868 pub fn renderFullyQualifiedName(file: File, writer: anytype) !void {
863869 // Convert all the slashes into dots and truncate the extension.
864870 const ext = std.fs.path.extension(file.sub_file_path);
......@@ -879,9 +885,12 @@ pub const File = struct {
879885 pub fn fullyQualifiedName(file: File, pt: Zcu.PerThread) !InternPool.NullTerminatedString {
880886 const gpa = pt.zcu.gpa;
881887 const ip = &pt.zcu.intern_pool;
882 const start = ip.string_bytes.items.len;
883 try file.renderFullyQualifiedName(ip.string_bytes.writer(gpa));
884 return ip.getOrPutTrailingString(gpa, pt.tid, ip.string_bytes.items.len - start, .no_embedded_nulls);
888 const strings = ip.getLocal(pt.tid).getMutableStrings(gpa);
889 const slice = try strings.addManyAsSlice(file.fullyQualifiedNameLen());
890 var fbs = std.io.fixedBufferStream(slice[0]);
891 file.renderFullyQualifiedName(fbs.writer()) catch unreachable;
892 assert(fbs.pos == slice[0].len);
893 return ip.getOrPutTrailingString(gpa, pt.tid, @intCast(slice[0].len), .no_embedded_nulls);
885894 }
886895
887896 pub fn fullPath(file: File, ally: Allocator) ![]u8 {
src/Zcu/PerThread.zig+6-5
......@@ -1377,10 +1377,11 @@ fn newEmbedFile(
13771377 };
13781378 const size = std.math.cast(usize, actual_stat.size) orelse return error.Overflow;
13791379
1380 const bytes = try ip.string_bytes.addManyAsSlice(gpa, try std.math.add(usize, size, 1));
1381 const actual_read = try file.readAll(bytes[0..size]);
1380 const strings = ip.getLocal(pt.tid).getMutableStrings(gpa);
1381 const bytes = try strings.addManyAsSlice(try std.math.add(usize, size, 1));
1382 const actual_read = try file.readAll(bytes[0][0..size]);
13821383 if (actual_read != size) return error.UnexpectedEndOfFile;
1383 bytes[size] = 0;
1384 bytes[0][size] = 0;
13841385
13851386 const comp = mod.comp;
13861387 switch (comp.cache_use) {
......@@ -1389,7 +1390,7 @@ fn newEmbedFile(
13891390 errdefer gpa.free(copied_resolved_path);
13901391 whole.cache_manifest_mutex.lock();
13911392 defer whole.cache_manifest_mutex.unlock();
1392 try man.addFilePostContents(copied_resolved_path, bytes[0..size], stat);
1393 try man.addFilePostContents(copied_resolved_path, bytes[0][0..size], stat);
13931394 },
13941395 .incremental => {},
13951396 }
......@@ -1401,7 +1402,7 @@ fn newEmbedFile(
14011402 } });
14021403 const array_val = try pt.intern(.{ .aggregate = .{
14031404 .ty = array_ty,
1404 .storage = .{ .bytes = try ip.getOrPutTrailingString(gpa, pt.tid, bytes.len, .maybe_embedded_nulls) },
1405 .storage = .{ .bytes = try ip.getOrPutTrailingString(gpa, pt.tid, @intCast(bytes[0].len), .maybe_embedded_nulls) },
14051406 } });
14061407
14071408 const ptr_ty = (try pt.ptrType(.{