InternPool: implement and use thread-safe list for strings

This commit is contained in:
Jacob Young 2024-07-03 22:37:09 -04:00
parent 3e1b190fe6
commit 8293ff94cf
6 changed files with 388 additions and 165 deletions

View File

@ -534,7 +534,7 @@ pub fn MultiArrayList(comptime T: type) type {
self.sortInternal(a, b, ctx, .unstable);
}
fn capacityInBytes(capacity: usize) usize {
pub fn capacityInBytes(capacity: usize) usize {
comptime var elem_bytes: usize = 0;
inline for (sizes.bytes) |size| elem_bytes += size;
return elem_bytes * capacity;

View File

@ -2748,7 +2748,7 @@ const Header = extern struct {
items_len: u32,
extra_len: u32,
limbs_len: u32,
string_bytes_len: u32,
//string_bytes_len: u32,
tracked_insts_len: u32,
src_hash_deps_len: u32,
decl_val_deps_len: u32,
@ -2777,7 +2777,7 @@ pub fn saveState(comp: *Compilation) !void {
.items_len = @intCast(ip.items.len),
.extra_len = @intCast(ip.extra.items.len),
.limbs_len = @intCast(ip.limbs.items.len),
.string_bytes_len = @intCast(ip.string_bytes.items.len),
//.string_bytes_len = @intCast(ip.string_bytes.items.len),
.tracked_insts_len = @intCast(ip.tracked_insts.count()),
.src_hash_deps_len = @intCast(ip.src_hash_deps.count()),
.decl_val_deps_len = @intCast(ip.decl_val_deps.count()),
@ -2794,7 +2794,7 @@ pub fn saveState(comp: *Compilation) !void {
addBuf(&bufs_list, &bufs_len, mem.sliceAsBytes(ip.extra.items));
addBuf(&bufs_list, &bufs_len, mem.sliceAsBytes(ip.items.items(.data)));
addBuf(&bufs_list, &bufs_len, mem.sliceAsBytes(ip.items.items(.tag)));
addBuf(&bufs_list, &bufs_len, ip.string_bytes.items);
//addBuf(&bufs_list, &bufs_len, ip.string_bytes.items);
addBuf(&bufs_list, &bufs_len, mem.sliceAsBytes(ip.tracked_insts.keys()));
addBuf(&bufs_list, &bufs_len, mem.sliceAsBytes(ip.src_hash_deps.keys()));

View File

@ -2,9 +2,11 @@
//! This data structure is self-contained, with the following exceptions:
//! * Module.Namespace has a pointer to Module.File
local: []Local = &.{},
shard_shift: std.math.Log2Int(usize) = 0,
locals: []Local = &.{},
shards: []Shard = &.{},
tid_width: std.math.Log2Int(u32) = 0,
tid_shift_31: std.math.Log2Int(u32) = 31,
tid_shift_32: std.math.Log2Int(u32) = 31,
items: std.MultiArrayList(Item) = .{},
extra: std.ArrayListUnmanaged(u32) = .{},
@ -13,12 +15,6 @@ extra: std.ArrayListUnmanaged(u32) = .{},
/// Use the helper methods instead of accessing this directly in order to not
/// violate the above mechanism.
limbs: std.ArrayListUnmanaged(u64) = .{},
/// In order to store references to strings in fewer bytes, we copy all
/// string bytes into here. String bytes can be null. It is up to whomever
/// is referencing the data here whether they want to store both index and length,
/// thus allowing null bytes, or store only index, and use null-termination. The
/// `string_bytes` array is agnostic to either usage.
string_bytes: std.ArrayListUnmanaged(u8) = .{},
/// Rather than allocating Decl objects with an Allocator, we instead allocate
/// them with this SegmentedList. This provides four advantages:
@ -345,52 +341,237 @@ pub const DepEntry = extern struct {
};
const Local = struct {
aligned: void align(std.atomic.cache_line) = {},
shared: Shared align(std.atomic.cache_line),
mutate: struct {
arena: std.heap.ArenaAllocator.State,
strings: Mutate,
} align(std.atomic.cache_line),
/// header: List.Header,
/// data: [capacity]u32,
/// tag: [header.capacity]Tag,
items: List,
/// header: List.Header,
/// extra: [header.capacity]u32,
extra: List,
/// header: List.Header,
/// bytes: [header.capacity]u8,
strings: List,
arena: std.heap.ArenaAllocator.State,
const List = struct {
entries: [*]u32,
const empty: List = .{ .entries = @constCast(&(extern struct {
header: Header,
entries: [0]u32,
}{
.header = .{ .len = 0, .capacity = 0 },
.entries = .{},
}).entries) };
fn acquire(list: *const List) List {
return .{ .entries = @atomicLoad([*]u32, &list.entries, .acquire) };
}
fn release(list: *List, new_list: List) void {
@atomicStore([*]u32, &list.entries, new_list.entries, .release);
}
const Header = extern struct {
len: u32,
capacity: u32,
const fields_len = @typeInfo(Header).Struct.fields.len;
};
fn header(list: List) *Header {
return @ptrCast(list.entries - Header.fields_len);
}
const Shared = struct {
strings: Strings,
};
const Strings = List(struct { u8 });
const Mutate = struct {
len: u32,
const empty: Mutate = .{
.len = 0,
};
};
fn List(comptime Elem: type) type {
assert(@typeInfo(Elem) == .Struct);
return struct {
bytes: [*]align(@alignOf(Elem)) u8,
const ListSelf = @This();
const Mutable = struct {
gpa: std.mem.Allocator,
arena: *std.heap.ArenaAllocator.State,
mutate: *Mutate,
list: *ListSelf,
const fields = std.enums.values(std.meta.FieldEnum(Elem));
fn Slice(comptime opts: struct { is_const: bool = false }) type {
const elem_info = @typeInfo(Elem).Struct;
const elem_fields = elem_info.fields;
var new_fields: [elem_fields.len]std.builtin.Type.StructField = undefined;
for (&new_fields, elem_fields) |*new_field, elem_field| new_field.* = .{
.name = elem_field.name,
.type = @Type(.{ .Pointer = .{
.size = .Slice,
.is_const = opts.is_const,
.is_volatile = false,
.alignment = 0,
.address_space = .generic,
.child = elem_field.type,
.is_allowzero = false,
.sentinel = null,
} }),
.default_value = null,
.is_comptime = false,
.alignment = 0,
};
return @Type(.{ .Struct = .{
.layout = .auto,
.fields = &new_fields,
.decls = &.{},
.is_tuple = elem_info.is_tuple,
} });
}
pub fn appendAssumeCapacity(mutable: Mutable, elem: Elem) void {
var mutable_view = mutable.view();
defer mutable.lenPtr().* = @intCast(mutable_view.len);
mutable_view.appendAssumeCapacity(elem);
}
pub fn appendSliceAssumeCapacity(
mutable: Mutable,
slice: Slice(.{ .is_const = true }),
) void {
if (fields.len == 0) return;
const mutable_len = mutable.lenPtr();
const start = mutable_len.*;
const slice_len = @field(slice, @tagName(fields[0])).len;
assert(slice_len < mutable.capacityPtr().* - start);
mutable_len.* = @intCast(start + slice_len);
const mutable_view = mutable.view();
inline for (fields) |field| {
const field_slice = @field(slice, @tagName(field));
assert(field_slice.len == slice_len);
@memcpy(mutable_view.items(field)[start..][0..slice_len], field_slice);
}
}
pub fn appendNTimes(mutable: Mutable, elem: Elem, len: usize) Allocator.Error!void {
try mutable.ensureUnusedCapacity(len);
mutable.appendNTimesAssumeCapacity(elem, len);
}
pub fn appendNTimesAssumeCapacity(mutable: Mutable, elem: Elem, len: usize) void {
const mutable_len = mutable.lenPtr();
const start = mutable_len.*;
assert(len <= mutable.capacityPtr().* - start);
mutable_len.* = @intCast(start + len);
const mutable_view = mutable.view();
inline for (fields) |field| {
@memset(mutable_view.items(field)[start..][0..len], @field(elem, @tagName(field)));
}
}
pub fn addManyAsSlice(mutable: Mutable, len: usize) Allocator.Error!Slice(.{}) {
try mutable.ensureUnusedCapacity(len);
return mutable.addManyAsSliceAssumeCapacity(len);
}
pub fn addManyAsSliceAssumeCapacity(mutable: Mutable, len: usize) Slice(.{}) {
const mutable_len = mutable.lenPtr();
const start = mutable_len.*;
assert(len <= mutable.capacityPtr().* - start);
mutable_len.* = @intCast(start + len);
const mutable_view = mutable.view();
var slice: Slice(.{}) = undefined;
inline for (fields) |field| {
@field(slice, @tagName(field)) = mutable_view.items(field)[start..][0..len];
}
return slice;
}
pub fn shrinkRetainingCapacity(mutable: Mutable, len: usize) void {
const mutable_len = mutable.lenPtr();
assert(len <= mutable_len.*);
mutable_len.* = @intCast(len);
}
pub fn ensureUnusedCapacity(mutable: Mutable, unused_capacity: usize) Allocator.Error!void {
try mutable.ensureTotalCapacity(@intCast(mutable.lenPtr().* + unused_capacity));
}
pub fn ensureTotalCapacity(mutable: Mutable, total_capacity: usize) Allocator.Error!void {
const old_capacity = mutable.capacityPtr().*;
if (old_capacity >= total_capacity) return;
var new_capacity = old_capacity;
while (new_capacity < total_capacity) new_capacity = (new_capacity + 10) * 2;
try mutable.setCapacity(new_capacity);
}
fn setCapacity(mutable: Mutable, capacity: u32) Allocator.Error!void {
var arena = mutable.arena.promote(mutable.gpa);
defer mutable.arena.* = arena.state;
const buf = try arena.allocator().alignedAlloc(
u8,
alignment,
bytes_offset + View.capacityInBytes(capacity),
);
var new_list: ListSelf = .{ .bytes = @ptrCast(buf[bytes_offset..].ptr) };
new_list.header().* = .{ .capacity = capacity };
const len = mutable.lenPtr().*;
const old_slice = mutable.list.view().slice();
const new_slice = new_list.view().slice();
inline for (fields) |field| {
@memcpy(new_slice.items(field)[0..len], old_slice.items(field)[0..len]);
}
mutable.list.release(new_list);
}
fn view(mutable: Mutable) View {
return .{
.bytes = mutable.list.bytes,
.len = mutable.lenPtr().*,
.capacity = mutable.capacityPtr().*,
};
}
pub fn lenPtr(mutable: Mutable) *u32 {
return &mutable.mutate.len;
}
pub fn capacityPtr(mutable: Mutable) *u32 {
return &mutable.list.header().capacity;
}
};
const empty: ListSelf = .{ .bytes = @constCast(&(extern struct {
header: Header,
bytes: [0]u8,
}{
.header = .{ .capacity = 0 },
.bytes = .{},
}).bytes) };
const alignment = @max(@alignOf(Header), @alignOf(Elem));
const bytes_offset = std.mem.alignForward(usize, @sizeOf(Header), @alignOf(Elem));
const View = std.MultiArrayList(Elem);
fn acquire(list: *const ListSelf) ListSelf {
return .{ .bytes = @atomicLoad([*]align(@alignOf(Elem)) u8, &list.bytes, .acquire) };
}
fn release(list: *ListSelf, new_list: ListSelf) void {
@atomicStore([*]align(@alignOf(Elem)) u8, &list.bytes, new_list.bytes, .release);
}
const Header = extern struct {
capacity: u32,
};
fn header(list: ListSelf) *Header {
return @ptrFromInt(@intFromPtr(list.bytes) - bytes_offset);
}
fn view(list: ListSelf) View {
const capacity = list.header().capacity;
return .{
.bytes = list.bytes,
.len = capacity,
.capacity = capacity,
};
}
};
}
/// In order to store references to strings in fewer bytes, we copy all
/// string bytes into here. String bytes can be null. It is up to whomever
/// is referencing the data here whether they want to store both index and length,
/// thus allowing null bytes, or store only index, and use null-termination. The
/// `strings` array is agnostic to either usage.
pub fn getMutableStrings(local: *Local, gpa: std.mem.Allocator) Strings.Mutable {
return .{
.gpa = gpa,
.arena = &local.mutate.arena,
.mutate = &local.mutate.strings,
.list = &local.shared.strings,
};
}
};
pub fn getLocal(ip: *InternPool, tid: Zcu.PerThread.Id) *Local {
return &ip.locals[@intFromEnum(tid)];
}
pub fn getLocalShared(ip: *const InternPool, tid: Zcu.PerThread.Id) *const Local.Shared {
return &ip.locals[@intFromEnum(tid)].shared;
}
const Shard = struct {
shared: struct {
@ -448,7 +629,7 @@ const Shard = struct {
}
};
fn header(map: @This()) *Header {
return &(@as([*]Header, @ptrCast(map.entries)) - 1)[0];
return @ptrFromInt(@intFromPtr(map.entries) - entries_offset);
}
const Entry = extern struct {
@ -465,6 +646,17 @@ const Shard = struct {
};
}
};
fn getShard(ip: *InternPool, tid: Zcu.PerThread.Id) *Shard {
return &ip.shards[@intFromEnum(tid)];
}
fn getTidMask(ip: *const InternPool) u32 {
assert(std.math.isPowerOfTwo(ip.shards.len));
return @intCast(ip.shards.len - 1);
}
fn getIndexMask(ip: *const InternPool, comptime BackingInt: type) u32 {
return @as(u32, std.math.maxInt(BackingInt)) >> ip.tid_width;
}
const FieldMap = std.ArrayHashMapUnmanaged(void, void, std.array_hash_map.AutoContext(void), false);
@ -560,18 +752,18 @@ pub const OptionalNamespaceIndex = enum(u32) {
}
};
/// An index into `string_bytes`.
/// An index into `strings`.
pub const String = enum(u32) {
/// An empty string.
empty = 0,
_,
pub fn toSlice(string: String, len: u64, ip: *const InternPool) []const u8 {
return ip.string_bytes.items[@intFromEnum(string)..][0..@intCast(len)];
return string.toOverlongSlice(ip)[0..@intCast(len)];
}
pub fn at(string: String, index: u64, ip: *const InternPool) u8 {
return ip.string_bytes.items[@intCast(@intFromEnum(string) + index)];
return string.toOverlongSlice(ip)[@intCast(index)];
}
pub fn toNullTerminatedString(string: String, len: u64, ip: *const InternPool) NullTerminatedString {
@ -579,9 +771,32 @@ pub const String = enum(u32) {
assert(string.at(len, ip) == 0);
return @enumFromInt(@intFromEnum(string));
}
const Unwrapped = struct {
tid: Zcu.PerThread.Id,
index: u32,
fn wrap(unwrapped: Unwrapped, ip: *const InternPool) String {
assert(@intFromEnum(unwrapped.tid) <= ip.getTidMask());
assert(unwrapped.index <= ip.getIndexMask(u32));
return @enumFromInt(@intFromEnum(unwrapped.tid) << ip.tid_shift_32 | unwrapped.index);
}
};
fn unwrap(string: String, ip: *const InternPool) Unwrapped {
return .{
.tid = @enumFromInt(@intFromEnum(string) >> ip.tid_shift_32 & ip.getTidMask()),
.index = @intFromEnum(string) & ip.getIndexMask(u32),
};
}
fn toOverlongSlice(string: String, ip: *const InternPool) []const u8 {
const unwrapped = string.unwrap(ip);
const strings = ip.getLocalShared(unwrapped.tid).strings.acquire();
return strings.view().items(.@"0")[unwrapped.index..];
}
};
/// An index into `string_bytes` which might be `none`.
/// An index into `strings` which might be `none`.
pub const OptionalString = enum(u32) {
/// This is distinct from `none` - it is a valid index that represents empty string.
empty = 0,
@ -597,7 +812,7 @@ pub const OptionalString = enum(u32) {
}
};
/// An index into `string_bytes`.
/// An index into `strings`.
pub const NullTerminatedString = enum(u32) {
/// An empty string.
empty = 0,
@ -623,12 +838,8 @@ pub const NullTerminatedString = enum(u32) {
return @enumFromInt(@intFromEnum(self));
}
fn toOverlongSlice(string: NullTerminatedString, ip: *const InternPool) []const u8 {
return ip.string_bytes.items[@intFromEnum(string)..];
}
pub fn toSlice(string: NullTerminatedString, ip: *const InternPool) [:0]const u8 {
const overlong_slice = string.toOverlongSlice(ip);
const overlong_slice = string.toString().toOverlongSlice(ip);
return overlong_slice[0..std.mem.indexOfScalar(u8, overlong_slice, 0).? :0];
}
@ -637,7 +848,7 @@ pub const NullTerminatedString = enum(u32) {
}
pub fn eqlSlice(string: NullTerminatedString, slice: []const u8, ip: *const InternPool) bool {
const overlong_slice = string.toOverlongSlice(ip);
const overlong_slice = string.toString().toOverlongSlice(ip);
return overlong_slice.len > slice.len and
std.mem.eql(u8, overlong_slice[0..slice.len], slice) and
overlong_slice[slice.len] == 0;
@ -688,12 +899,12 @@ pub const NullTerminatedString = enum(u32) {
} else @compileError("invalid format string '" ++ specifier ++ "' for '" ++ @typeName(NullTerminatedString) ++ "'");
}
pub fn fmt(self: NullTerminatedString, ip: *const InternPool) std.fmt.Formatter(format) {
return .{ .data = .{ .string = self, .ip = ip } };
pub fn fmt(string: NullTerminatedString, ip: *const InternPool) std.fmt.Formatter(format) {
return .{ .data = .{ .string = string, .ip = ip } };
}
};
/// An index into `string_bytes` which might be `none`.
/// An index into `strings` which might be `none`.
pub const OptionalNullTerminatedString = enum(u32) {
/// This is distinct from `none` - it is a valid index that represents empty string.
empty = 0,
@ -4077,7 +4288,7 @@ pub const FuncAnalysis = packed struct(u32) {
pub const Bytes = struct {
/// The type of the aggregate
ty: Index,
/// Index into string_bytes, of len ip.aggregateTypeLen(ty)
/// Index into strings, of len ip.aggregateTypeLen(ty)
bytes: String,
};
@ -4647,16 +4858,21 @@ pub fn init(ip: *InternPool, gpa: Allocator, total_threads: usize) !void {
errdefer ip.deinit(gpa);
assert(ip.items.len == 0);
ip.local = try gpa.alloc(Local, total_threads);
@memset(ip.local, .{
.items = Local.List.empty,
.extra = Local.List.empty,
.strings = Local.List.empty,
.arena = .{},
ip.locals = try gpa.alloc(Local, total_threads);
@memset(ip.locals, .{
.shared = .{
.strings = Local.Strings.empty,
},
.mutate = .{
.arena = .{},
.strings = Local.Mutate.empty,
},
});
ip.shard_shift = @intCast(std.math.log2_int_ceil(usize, total_threads));
ip.shards = try gpa.alloc(Shard, @as(usize, 1) << ip.shard_shift);
ip.tid_width = @intCast(std.math.log2_int_ceil(usize, total_threads));
ip.tid_shift_31 = 31 - ip.tid_width;
ip.tid_shift_32 = ip.tid_shift_31 +| 1;
ip.shards = try gpa.alloc(Shard, @as(usize, 1) << ip.tid_width);
@memset(ip.shards, .{
.shared = .{
.map = Shard.Map(Index).empty,
@ -4705,7 +4921,6 @@ pub fn deinit(ip: *InternPool, gpa: Allocator) void {
ip.items.deinit(gpa);
ip.extra.deinit(gpa);
ip.limbs.deinit(gpa);
ip.string_bytes.deinit(gpa);
ip.decls_free_list.deinit(gpa);
ip.allocated_decls.deinit(gpa);
@ -4732,8 +4947,8 @@ pub fn deinit(ip: *InternPool, gpa: Allocator) void {
ip.files.deinit(gpa);
gpa.free(ip.shards);
for (ip.local) |*local| local.arena.promote(gpa).deinit();
gpa.free(ip.local);
for (ip.locals) |*local| local.mutate.arena.promote(gpa).deinit();
gpa.free(ip.locals);
ip.* = undefined;
}
@ -5437,8 +5652,9 @@ fn getOrPutKey(
}
const map_header = map.header().*;
if (shard.mutate.map.len >= map_header.capacity * 3 / 5) {
var arena = ip.local[@intFromEnum(tid)].arena.promote(gpa);
defer ip.local[@intFromEnum(tid)].arena = arena.state;
const arena_state = &ip.getLocal(tid).mutate.arena;
var arena = arena_state.promote(gpa);
defer arena_state.* = arena.state;
const new_map_capacity = map_header.capacity * 2;
const new_map_buf = try arena.allocator().alignedAlloc(
u8,
@ -6194,33 +6410,32 @@ pub fn get(ip: *InternPool, gpa: Allocator, tid: Zcu.PerThread.Id, key: Key) All
}
if (child == .u8_type) bytes: {
const string_bytes_index = ip.string_bytes.items.len;
try ip.string_bytes.ensureUnusedCapacity(gpa, @intCast(len_including_sentinel + 1));
const strings = ip.getLocal(tid).getMutableStrings(gpa);
const start = strings.lenPtr().*;
try strings.ensureUnusedCapacity(@intCast(len_including_sentinel + 1));
try ip.extra.ensureUnusedCapacity(gpa, @typeInfo(Bytes).Struct.fields.len);
switch (aggregate.storage) {
.bytes => |bytes| ip.string_bytes.appendSliceAssumeCapacity(bytes.toSlice(len, ip)),
.bytes => |bytes| strings.appendSliceAssumeCapacity(.{bytes.toSlice(len, ip)}),
.elems => |elems| for (elems[0..@intCast(len)]) |elem| switch (ip.indexToKey(elem)) {
.undef => {
ip.string_bytes.shrinkRetainingCapacity(string_bytes_index);
strings.shrinkRetainingCapacity(start);
break :bytes;
},
.int => |int| ip.string_bytes.appendAssumeCapacity(
@intCast(int.storage.u64),
),
.int => |int| strings.appendAssumeCapacity(.{@intCast(int.storage.u64)}),
else => unreachable,
},
.repeated_elem => |elem| switch (ip.indexToKey(elem)) {
.undef => break :bytes,
.int => |int| @memset(
ip.string_bytes.addManyAsSliceAssumeCapacity(@intCast(len)),
strings.addManyAsSliceAssumeCapacity(@intCast(len))[0],
@intCast(int.storage.u64),
),
else => unreachable,
},
}
if (sentinel != .none) ip.string_bytes.appendAssumeCapacity(
if (sentinel != .none) strings.appendAssumeCapacity(.{
@intCast(ip.indexToKey(sentinel).int.storage.u64),
);
});
const string = try ip.getOrPutTrailingString(
gpa,
tid,
@ -9050,10 +9265,11 @@ pub fn getOrPutString(
slice: []const u8,
comptime embedded_nulls: EmbeddedNulls,
) Allocator.Error!embedded_nulls.StringType() {
try ip.string_bytes.ensureUnusedCapacity(gpa, slice.len + 1);
ip.string_bytes.appendSliceAssumeCapacity(slice);
ip.string_bytes.appendAssumeCapacity(0);
return ip.getOrPutTrailingString(gpa, tid, slice.len + 1, embedded_nulls);
const strings = ip.getLocal(tid).getMutableStrings(gpa);
try strings.ensureUnusedCapacity(slice.len + 1);
strings.appendSliceAssumeCapacity(.{slice});
strings.appendAssumeCapacity(.{0});
return ip.getOrPutTrailingString(gpa, tid, @intCast(slice.len + 1), embedded_nulls);
}
pub fn getOrPutStringFmt(
@ -9064,11 +9280,12 @@ pub fn getOrPutStringFmt(
args: anytype,
comptime embedded_nulls: EmbeddedNulls,
) Allocator.Error!embedded_nulls.StringType() {
// ensure that references to string_bytes in args do not get invalidated
const len: usize = @intCast(std.fmt.count(format, args) + 1);
try ip.string_bytes.ensureUnusedCapacity(gpa, len);
ip.string_bytes.writer(undefined).print(format, args) catch unreachable;
ip.string_bytes.appendAssumeCapacity(0);
// ensure that references to strings in args do not get invalidated
const format_z = format ++ .{0};
const len: u32 = @intCast(std.fmt.count(format_z, args));
const strings = ip.getLocal(tid).getMutableStrings(gpa);
const slice = try strings.addManyAsSlice(len);
assert((std.fmt.bufPrint(slice[0], format_z, args) catch unreachable).len == len);
return ip.getOrPutTrailingString(gpa, tid, len, embedded_nulls);
}
@ -9083,47 +9300,33 @@ pub fn getOrPutStringOpt(
return string.toOptional();
}
/// Uses the last len bytes of ip.string_bytes as the key.
/// Uses the last len bytes of strings as the key.
pub fn getOrPutTrailingString(
ip: *InternPool,
gpa: Allocator,
tid: Zcu.PerThread.Id,
len: usize,
len: u32,
comptime embedded_nulls: EmbeddedNulls,
) Allocator.Error!embedded_nulls.StringType() {
const string_bytes = &ip.string_bytes;
const str_index: u32 = @intCast(string_bytes.items.len - len);
if (len > 0 and string_bytes.getLast() == 0) {
_ = string_bytes.pop();
const strings = ip.getLocal(tid).getMutableStrings(gpa);
const start: u32 = @intCast(strings.lenPtr().* - len);
if (len > 0 and strings.view().items(.@"0")[strings.lenPtr().* - 1] == 0) {
strings.lenPtr().* -= 1;
} else {
try string_bytes.ensureUnusedCapacity(gpa, 1);
try strings.ensureUnusedCapacity(1);
}
const key: []const u8 = string_bytes.items[str_index..];
const key: []const u8 = strings.view().items(.@"0")[start..];
const value: embedded_nulls.StringType() =
@enumFromInt(@intFromEnum(tid) << ip.tid_shift_32 | start);
const has_embedded_null = std.mem.indexOfScalar(u8, key, 0) != null;
switch (embedded_nulls) {
.no_embedded_nulls => assert(!has_embedded_null),
.maybe_embedded_nulls => if (has_embedded_null) {
string_bytes.appendAssumeCapacity(0);
return @enumFromInt(str_index);
strings.appendAssumeCapacity(.{0});
return value;
},
}
const maybe_existing_index = try ip.getOrPutStringValue(gpa, tid, key, @enumFromInt(str_index));
if (maybe_existing_index.unwrap()) |existing_index| {
string_bytes.shrinkRetainingCapacity(str_index);
return @enumFromInt(@intFromEnum(existing_index));
} else {
string_bytes.appendAssumeCapacity(0);
return @enumFromInt(str_index);
}
}
fn getOrPutStringValue(
ip: *InternPool,
gpa: Allocator,
tid: Zcu.PerThread.Id,
key: []const u8,
value: NullTerminatedString,
) Allocator.Error!OptionalNullTerminatedString {
const full_hash = Hash.hash(0, key);
const hash: u32 = @truncate(full_hash >> 32);
const shard = &ip.shards[@intCast(full_hash & (ip.shards.len - 1))];
@ -9136,7 +9339,9 @@ fn getOrPutStringValue(
const entry = &map.entries[map_index];
const index = entry.acquire().unwrap() orelse break;
if (entry.hash != hash) continue;
if (index.eqlSlice(key, ip)) return index.toOptional();
if (!index.eqlSlice(key, ip)) continue;
strings.shrinkRetainingCapacity(start);
return @enumFromInt(@intFromEnum(index));
}
shard.mutate.string_map.mutex.lock();
defer shard.mutate.string_map.mutex.unlock();
@ -9151,18 +9356,22 @@ fn getOrPutStringValue(
const entry = &map.entries[map_index];
const index = entry.acquire().unwrap() orelse break;
if (entry.hash != hash) continue;
if (index.eqlSlice(key, ip)) return index.toOptional();
if (!index.eqlSlice(key, ip)) continue;
strings.shrinkRetainingCapacity(start);
return @enumFromInt(@intFromEnum(index));
}
defer shard.mutate.string_map.len += 1;
const map_header = map.header().*;
if (shard.mutate.string_map.len < map_header.capacity * 3 / 5) {
const entry = &map.entries[map_index];
entry.hash = hash;
entry.release(value.toOptional());
return .none;
entry.release(@enumFromInt(@intFromEnum(value)));
strings.appendAssumeCapacity(.{0});
return value;
}
var arena = ip.local[@intFromEnum(tid)].arena.promote(gpa);
defer ip.local[@intFromEnum(tid)].arena = arena.state;
const arena_state = &ip.getLocal(tid).mutate.arena;
var arena = arena_state.promote(gpa);
defer arena_state.* = arena.state;
const new_map_capacity = map_header.capacity * 2;
const new_map_buf = try arena.allocator().alignedAlloc(
u8,
@ -9197,11 +9406,12 @@ fn getOrPutStringValue(
if (map.entries[map_index].value == .none) break;
}
map.entries[map_index] = .{
.value = value.toOptional(),
.value = @enumFromInt(@intFromEnum(value)),
.hash = hash,
};
shard.shared.string_map.release(new_map);
return .none;
strings.appendAssumeCapacity(.{0});
return value;
}
pub fn getString(ip: *InternPool, key: []const u8) OptionalNullTerminatedString {

View File

@ -65,8 +65,9 @@ pub fn toIpString(val: Value, ty: Type, pt: Zcu.PerThread) !InternPool.NullTermi
.elems => return arrayToIpString(val, ty.arrayLen(mod), pt),
.repeated_elem => |elem| {
const byte: u8 = @intCast(Value.fromInterned(elem).toUnsignedInt(pt));
const len: usize = @intCast(ty.arrayLen(mod));
try ip.string_bytes.appendNTimes(mod.gpa, byte, len);
const len: u32 = @intCast(ty.arrayLen(mod));
const strings = ip.getLocal(pt.tid).getMutableStrings(mod.gpa);
try strings.appendNTimes(.{byte}, len);
return ip.getOrPutTrailingString(mod.gpa, pt.tid, len, .no_embedded_nulls);
},
}
@ -107,16 +108,18 @@ fn arrayToIpString(val: Value, len_u64: u64, pt: Zcu.PerThread) !InternPool.Null
const mod = pt.zcu;
const gpa = mod.gpa;
const ip = &mod.intern_pool;
const len: usize = @intCast(len_u64);
try ip.string_bytes.ensureUnusedCapacity(gpa, len);
const len: u32 = @intCast(len_u64);
const strings = ip.getLocal(pt.tid).getMutableStrings(gpa);
const strings_len = strings.lenPtr();
try strings.ensureUnusedCapacity(len);
for (0..len) |i| {
// I don't think elemValue has the possibility to affect ip.string_bytes. Let's
// assert just to be sure.
const prev = ip.string_bytes.items.len;
const prev_len = strings_len.*;
const elem_val = try val.elemValue(pt, i);
assert(ip.string_bytes.items.len == prev);
assert(strings_len.* == prev_len);
const byte: u8 = @intCast(elem_val.toUnsignedInt(pt));
ip.string_bytes.appendAssumeCapacity(byte);
strings.appendAssumeCapacity(.{byte});
}
return ip.getOrPutTrailingString(gpa, pt.tid, len, .no_embedded_nulls);
}

View File

@ -693,38 +693,39 @@ pub const Namespace = struct {
) !InternPool.NullTerminatedString {
const zcu = pt.zcu;
const ip = &zcu.intern_pool;
const count = count: {
const gpa = zcu.gpa;
const strings = ip.getLocal(pt.tid).getMutableStrings(gpa);
// Protects reads of interned strings from being reallocated during the call to
// renderFullyQualifiedName.
const slice = try strings.addManyAsSlice(count: {
var count: usize = name.length(ip) + 1;
var cur_ns = &ns;
while (true) {
const decl = zcu.declPtr(cur_ns.decl_index);
count += decl.name.length(ip) + 1;
cur_ns = zcu.namespacePtr(cur_ns.parent.unwrap() orelse {
count += ns.fileScope(zcu).sub_file_path.len;
count += ns.fileScope(zcu).fullyQualifiedNameLen();
break :count count;
});
count += decl.name.length(ip) + 1;
}
};
const gpa = zcu.gpa;
const start = ip.string_bytes.items.len;
// Protects reads of interned strings from being reallocated during the call to
// renderFullyQualifiedName.
try ip.string_bytes.ensureUnusedCapacity(gpa, count);
ns.renderFullyQualifiedName(zcu, name, ip.string_bytes.writer(gpa)) catch unreachable;
});
var fbs = std.io.fixedBufferStream(slice[0]);
ns.renderFullyQualifiedName(zcu, name, fbs.writer()) catch unreachable;
assert(fbs.pos == slice[0].len);
// Sanitize the name for nvptx which is more restrictive.
// TODO This should be handled by the backend, not the frontend. Have a
// look at how the C backend does it for inspiration.
const cpu_arch = zcu.root_mod.resolved_target.result.cpu.arch;
if (cpu_arch.isNvptx()) {
for (ip.string_bytes.items[start..]) |*byte| switch (byte.*) {
for (slice[0]) |*byte| switch (byte.*) {
'{', '}', '*', '[', ']', '(', ')', ',', ' ', '\'' => byte.* = '_',
else => {},
};
}
return ip.getOrPutTrailingString(gpa, pt.tid, ip.string_bytes.items.len - start, .no_embedded_nulls);
return ip.getOrPutTrailingString(gpa, pt.tid, @intCast(slice[0].len), .no_embedded_nulls);
}
pub fn getType(ns: Namespace, zcu: *Zcu) Type {
@ -859,6 +860,11 @@ pub const File = struct {
return &file.tree;
}
pub fn fullyQualifiedNameLen(file: File) usize {
const ext = std.fs.path.extension(file.sub_file_path);
return file.sub_file_path.len - ext.len;
}
pub fn renderFullyQualifiedName(file: File, writer: anytype) !void {
// Convert all the slashes into dots and truncate the extension.
const ext = std.fs.path.extension(file.sub_file_path);
@ -879,9 +885,12 @@ pub const File = struct {
pub fn fullyQualifiedName(file: File, pt: Zcu.PerThread) !InternPool.NullTerminatedString {
const gpa = pt.zcu.gpa;
const ip = &pt.zcu.intern_pool;
const start = ip.string_bytes.items.len;
try file.renderFullyQualifiedName(ip.string_bytes.writer(gpa));
return ip.getOrPutTrailingString(gpa, pt.tid, ip.string_bytes.items.len - start, .no_embedded_nulls);
const strings = ip.getLocal(pt.tid).getMutableStrings(gpa);
const slice = try strings.addManyAsSlice(file.fullyQualifiedNameLen());
var fbs = std.io.fixedBufferStream(slice[0]);
file.renderFullyQualifiedName(fbs.writer()) catch unreachable;
assert(fbs.pos == slice[0].len);
return ip.getOrPutTrailingString(gpa, pt.tid, @intCast(slice[0].len), .no_embedded_nulls);
}
pub fn fullPath(file: File, ally: Allocator) ![]u8 {

View File

@ -1377,10 +1377,11 @@ fn newEmbedFile(
};
const size = std.math.cast(usize, actual_stat.size) orelse return error.Overflow;
const bytes = try ip.string_bytes.addManyAsSlice(gpa, try std.math.add(usize, size, 1));
const actual_read = try file.readAll(bytes[0..size]);
const strings = ip.getLocal(pt.tid).getMutableStrings(gpa);
const bytes = try strings.addManyAsSlice(try std.math.add(usize, size, 1));
const actual_read = try file.readAll(bytes[0][0..size]);
if (actual_read != size) return error.UnexpectedEndOfFile;
bytes[size] = 0;
bytes[0][size] = 0;
const comp = mod.comp;
switch (comp.cache_use) {
@ -1389,7 +1390,7 @@ fn newEmbedFile(
errdefer gpa.free(copied_resolved_path);
whole.cache_manifest_mutex.lock();
defer whole.cache_manifest_mutex.unlock();
try man.addFilePostContents(copied_resolved_path, bytes[0..size], stat);
try man.addFilePostContents(copied_resolved_path, bytes[0][0..size], stat);
},
.incremental => {},
}
@ -1401,7 +1402,7 @@ fn newEmbedFile(
} });
const array_val = try pt.intern(.{ .aggregate = .{
.ty = array_ty,
.storage = .{ .bytes = try ip.getOrPutTrailingString(gpa, pt.tid, bytes.len, .maybe_embedded_nulls) },
.storage = .{ .bytes = try ip.getOrPutTrailingString(gpa, pt.tid, @intCast(bytes[0].len), .maybe_embedded_nulls) },
} });
const ptr_ty = (try pt.ptrType(.{