From accd5701c251c2741479fe08e56c8271c444f021 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 24 Aug 2023 20:43:43 -0700 Subject: compiler: move struct types into InternPool proper Structs were previously using `SegmentedList` to be given indexes, but were not actually backed by the InternPool arrays. After this, the only remaining uses of `SegmentedList` in the compiler are `Module.Decl` and `Module.Namespace`. Once those last two are migrated to become backed by InternPool arrays as well, we can introduce state serialization via writing these arrays to disk all at once. Unfortunately there are a lot of source code locations that touch the struct type API, so this commit is still work-in-progress. Once I get it compiling and passing the test suite, I can provide some interesting data points such as how it affected the InternPool memory size and performance comparison against master branch. I also couldn't resist migrating over a bunch of alignment API over to use the log2 Alignment type rather than a mismash of u32 and u64 byte units with 0 meaning something implicitly different and special at every location. Turns out you can do all the math you need directly on the log2 representation of alignments. --- src/InternPool.zig | 830 +++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 646 insertions(+), 184 deletions(-) (limited to 'src/InternPool.zig') diff --git a/src/InternPool.zig b/src/InternPool.zig index 85266954c9..78f518f2c0 100644 --- a/src/InternPool.zig +++ b/src/InternPool.zig @@ -1,7 +1,7 @@ //! All interned objects have both a value and a type. //! This data structure is self-contained, with the following exceptions: -//! * type_struct via Module.Struct.Index -//! * type_opaque via Module.Namespace.Index and Module.Decl.Index +//! * Module.Namespace has a pointer to Module.File +//! * Module.Decl has a pointer to Module.CaptureScope /// Maps `Key` to `Index`. `Key` objects are not stored anywhere; they are /// constructed lazily. @@ -39,17 +39,11 @@ allocated_namespaces: std.SegmentedList(Module.Namespace, 0) = .{}, /// Same pattern as with `decls_free_list`. namespaces_free_list: std.ArrayListUnmanaged(Module.Namespace.Index) = .{}, -/// Struct objects are stored in this data structure because: -/// * They contain pointers such as the field maps. -/// * They need to be mutated after creation. -allocated_structs: std.SegmentedList(Module.Struct, 0) = .{}, -/// When a Struct object is freed from `allocated_structs`, it is pushed into this stack. -structs_free_list: std.ArrayListUnmanaged(Module.Struct.Index) = .{}, - /// Some types such as enums, structs, and unions need to store mappings from field names /// to field index, or value to field index. In such cases, they will store the underlying /// field names and values directly, relying on one of these maps, stored separately, /// to provide lookup. +/// These are not serialized; it is computed upon deserialization. maps: std.ArrayListUnmanaged(FieldMap) = .{}, /// Used for finding the index inside `string_bytes`. @@ -365,11 +359,264 @@ pub const Key = union(enum) { namespace: Module.Namespace.Index, }; - pub const StructType = extern struct { - /// The `none` tag is used to represent a struct with no fields. - index: Module.Struct.OptionalIndex, - /// May be `none` if the struct has no declarations. + /// Although packed structs and non-packed structs are encoded differently, + /// this struct is used for both categories since they share some common + /// functionality. + pub const StructType = struct { + extra_index: u32, + /// `none` when the struct is `@TypeOf(.{})`. + decl: Module.Decl.OptionalIndex, + /// `none` when the struct has no declarations. namespace: Module.Namespace.OptionalIndex, + /// Index of the struct_decl ZIR instruction. + zir_index: Zir.Inst.Index, + layout: std.builtin.Type.ContainerLayout, + field_names: NullTerminatedString.Slice, + field_types: Index.Slice, + field_inits: Index.Slice, + field_aligns: Alignment.Slice, + runtime_order: RuntimeOrder.Slice, + comptime_bits: ComptimeBits, + offsets: Offsets, + names_map: MapIndex, + + pub const ComptimeBits = struct { + start: u32, + len: u32, + + pub fn get(this: @This(), ip: *const InternPool) []u32 { + return ip.extra.items[this.start..][0..this.len]; + } + + pub fn getBit(this: @This(), ip: *const InternPool, i: usize) bool { + if (this.len == 0) return false; + return @as(u1, @truncate(this.get(ip)[i / 32] >> @intCast(i % 32))) != 0; + } + + pub fn setBit(this: @This(), ip: *const InternPool, i: usize) void { + this.get(ip)[i / 32] |= @as(u32, 1) << @intCast(i % 32); + } + + pub fn clearBit(this: @This(), ip: *const InternPool, i: usize) void { + this.get(ip)[i / 32] &= ~(@as(u32, 1) << @intCast(i % 32)); + } + }; + + pub const Offsets = struct { + start: u32, + len: u32, + + pub fn get(this: @This(), ip: *const InternPool) []u32 { + return @ptrCast(ip.extra.items[this.start..][0..this.len]); + } + }; + + pub const RuntimeOrder = enum(u32) { + /// Placeholder until layout is resolved. + unresolved = std.math.maxInt(u32) - 0, + /// Field not present at runtime + omitted = std.math.maxInt(u32) - 1, + _, + + pub const Slice = struct { + start: u32, + len: u32, + + pub fn get(slice: Slice, ip: *const InternPool) []RuntimeOrder { + return @ptrCast(ip.extra.items[slice.start..][0..slice.len]); + } + }; + + pub fn toInt(i: @This()) ?u32 { + return switch (i) { + .omitted => null, + .unresolved => unreachable, + else => @intFromEnum(i), + }; + } + }; + + /// Look up field index based on field name. + pub fn nameIndex(self: StructType, ip: *const InternPool, name: NullTerminatedString) ?u32 { + if (self.decl == .none) return null; // empty_struct_type + const map = &ip.maps.items[@intFromEnum(self.names_map)]; + const adapter: NullTerminatedString.Adapter = .{ .strings = self.field_names.get(ip) }; + const field_index = map.getIndexAdapted(name, adapter) orelse return null; + return @intCast(field_index); + } + + /// Returns the already-existing field with the same name, if any. + pub fn addFieldName( + self: @This(), + ip: *InternPool, + name: NullTerminatedString, + ) ?u32 { + return ip.addFieldName(self.names_map, self.field_names.start, name); + } + + pub fn fieldAlign(s: @This(), ip: *const InternPool, i: usize) Alignment { + if (s.field_aligns.len == 0) return .none; + return s.field_aligns.get(ip)[i]; + } + + pub fn fieldInit(s: @This(), ip: *const InternPool, i: usize) Index { + if (s.field_inits.len == 0) return .none; + return s.field_inits.get(ip)[i]; + } + + /// Returns `none` in the case the struct is a tuple. + pub fn fieldName(s: @This(), ip: *const InternPool, i: usize) OptionalNullTerminatedString { + if (s.field_names.len == 0) return .none; + return s.field_names.get(ip)[i].toOptional(); + } + + pub fn fieldIsComptime(s: @This(), ip: *const InternPool, i: usize) bool { + return s.comptime_bits.getBit(ip, i); + } + + pub fn setFieldComptime(s: @This(), ip: *InternPool, i: usize) void { + s.comptime_bits.setBit(ip, i); + } + + /// Reads the non-opv flag calculated during AstGen. Used to short-circuit more + /// complicated logic. + pub fn knownNonOpv(s: @This(), ip: *InternPool) bool { + return switch (s.layout) { + .Packed => false, + .Auto, .Extern => s.flagsPtr(ip).known_non_opv, + }; + } + + /// The returned pointer expires with any addition to the `InternPool`. + /// Asserts the struct is not packed. + pub fn flagsPtr(self: @This(), ip: *InternPool) *Tag.TypeStruct.Flags { + assert(self.layout != .Packed); + const flags_field_index = std.meta.fieldIndex(Tag.TypeStruct, "flags").?; + return @ptrCast(&ip.extra.items[self.extra_index + flags_field_index]); + } + + pub fn assumeRuntimeBitsIfFieldTypesWip(s: @This(), ip: *InternPool) bool { + if (s.layout == .Packed) return false; + const flags_ptr = s.flagsPtr(ip); + if (flags_ptr.field_types_wip) { + flags_ptr.assumed_runtime_bits = true; + return true; + } + return false; + } + + pub fn setLayoutWip(s: @This(), ip: *InternPool) bool { + if (s.layout == .Packed) return false; + const flags_ptr = s.flagsPtr(ip); + if (flags_ptr.field_types_wip or flags_ptr.layout_wip) { + return true; + } + flags_ptr.layout_wip = true; + return false; + } + + pub fn setFullyResolved(s: @This(), ip: *InternPool) bool { + if (s.layout == .Packed) return true; + const flags_ptr = s.flagsPtr(ip); + if (flags_ptr.fully_resolved) return true; + flags_ptr.fully_resolved = true; + return false; + } + + pub fn clearFullyResolved(s: @This(), ip: *InternPool) void { + s.flagsPtr(ip).fully_resolved = false; + } + + pub fn setRequiresComptime(s: @This(), ip: *InternPool) void { + assert(s.layout != .Packed); + const flags_ptr = s.flagsPtr(ip); + // Layout is resolved (and non-existent) in the case of a comptime-known struct. + flags_ptr.layout_resolved = true; + flags_ptr.requires_comptime = .yes; + } + + /// The returned pointer expires with any addition to the `InternPool`. + /// Asserts the struct is not packed. + pub fn size(self: @This(), ip: *InternPool) *u32 { + assert(self.layout != .Packed); + const size_field_index = std.meta.fieldIndex(Tag.TypeStruct, "size").?; + return @ptrCast(&ip.extra.items[self.extra_index + size_field_index]); + } + + /// The backing integer type of the packed struct. Whether zig chooses + /// this type or the user specifies it, it is stored here. This will be + /// set to `none` until the layout is resolved. + /// Asserts the struct is packed. + pub fn backingIntType(s: @This(), ip: *const InternPool) *Index { + assert(s.layout == .Packed); + const field_index = std.meta.fieldIndex(Tag.TypeStructPacked, "backing_int_ty").?; + return @ptrCast(&ip.extra.items[s.extra_index + field_index]); + } + + /// Asserts the struct is not packed. + pub fn setZirIndex(s: @This(), ip: *InternPool, new_zir_index: Zir.Inst.Index) void { + assert(s.layout != .Packed); + const field_index = std.meta.fieldIndex(Tag.TypeStruct, "zir_index").?; + ip.extra.items[s.extra_index + field_index] = new_zir_index; + } + + pub fn haveFieldTypes(s: @This(), ip: *const InternPool) bool { + const types = s.field_types.get(ip); + return types.len == 0 or types[0] != .none; + } + + pub fn haveLayout(s: @This(), ip: *InternPool) bool { + return switch (s.layout) { + .Packed => s.haveFieldTypes(ip), + .Auto, .Extern => s.flagsPtr(ip).layout_resolved, + }; + } + + pub fn isTuple(s: @This(), ip: *InternPool) bool { + return s.layout != .Packed and s.flagsPtr(ip).is_tuple; + } + + pub fn hasReorderedFields(s: @This(), ip: *InternPool) bool { + return s.layout == .Auto and s.flagsPtr(ip).has_reordered_fields; + } + + pub const RuntimeOrderIterator = struct { + ip: *InternPool, + field_index: u32, + struct_type: InternPool.Key.StructType, + + pub fn next(it: *@This()) ?u32 { + var i = it.field_index; + + if (i >= it.struct_type.field_types.len) + return null; + + if (it.struct_type.hasReorderedFields(it.ip)) { + it.field_index += 1; + return it.struct_type.runtime_order.get(it.ip)[i].toInt(); + } + + while (it.struct_type.fieldIsComptime(it.ip, i)) { + i += 1; + if (i >= it.struct_type.field_types.len) + return null; + } + + it.field_index = i + 1; + return i; + } + }; + + /// Iterates over non-comptime fields in the order they are laid out in memory at runtime. + /// Asserts the struct is not packed. + pub fn iterateRuntimeOrder(s: @This(), ip: *InternPool) RuntimeOrderIterator { + assert(s.layout != .Packed); + return .{ + .ip = ip, + .field_index = 0, + .struct_type = s, + }; + } }; pub const AnonStructType = struct { @@ -870,7 +1117,6 @@ pub const Key = union(enum) { .simple_type, .simple_value, .opt, - .struct_type, .undef, .err, .enum_literal, @@ -893,6 +1139,7 @@ pub const Key = union(enum) { .enum_type, .variable, .union_type, + .struct_type, => |x| Hash.hash(seed, asBytes(&x.decl)), .int => |int| { @@ -969,11 +1216,11 @@ pub const Key = union(enum) { if (child == .u8_type) { switch (aggregate.storage) { - .bytes => |bytes| for (bytes[0..@as(usize, @intCast(len))]) |byte| { + .bytes => |bytes| for (bytes[0..@intCast(len)]) |byte| { std.hash.autoHash(&hasher, KeyTag.int); std.hash.autoHash(&hasher, byte); }, - .elems => |elems| for (elems[0..@as(usize, @intCast(len))]) |elem| { + .elems => |elems| for (elems[0..@intCast(len)]) |elem| { const elem_key = ip.indexToKey(elem); std.hash.autoHash(&hasher, @as(KeyTag, elem_key)); switch (elem_key) { @@ -1123,10 +1370,6 @@ pub const Key = union(enum) { const b_info = b.opt; return std.meta.eql(a_info, b_info); }, - .struct_type => |a_info| { - const b_info = b.struct_type; - return std.meta.eql(a_info, b_info); - }, .un => |a_info| { const b_info = b.un; return std.meta.eql(a_info, b_info); @@ -1298,6 +1541,10 @@ pub const Key = union(enum) { const b_info = b.union_type; return a_info.decl == b_info.decl; }, + .struct_type => |a_info| { + const b_info = b.struct_type; + return a_info.decl == b_info.decl; + }, .aggregate => |a_info| { const b_info = b.aggregate; if (a_info.ty != b_info.ty) return false; @@ -1433,6 +1680,8 @@ pub const Key = union(enum) { } }; +pub const RequiresComptime = enum(u2) { no, yes, unknown, wip }; + // Unlike `Tag.TypeUnion` which is an encoding, and `Key.UnionType` which is a // minimal hashmap key, this type is a convenience type that contains info // needed by semantic analysis. @@ -1474,8 +1723,6 @@ pub const UnionType = struct { } }; - pub const RequiresComptime = enum(u2) { no, yes, unknown, wip }; - pub const Status = enum(u3) { none, field_types_wip, @@ -1814,9 +2061,11 @@ pub const Index = enum(u32) { type_enum_nonexhaustive: DataIsExtraIndexOfEnumExplicit, simple_type: struct { data: SimpleType }, type_opaque: struct { data: *Key.OpaqueType }, - type_struct: struct { data: Module.Struct.OptionalIndex }, + type_struct: struct { data: *Tag.TypeStruct }, type_struct_ns: struct { data: Module.Namespace.Index }, type_struct_anon: DataIsExtraIndexOfTypeStructAnon, + type_struct_packed: struct { data: *Tag.TypeStructPacked }, + type_struct_packed_inits: struct { data: *Tag.TypeStructPacked }, type_tuple_anon: DataIsExtraIndexOfTypeStructAnon, type_union: struct { data: *Tag.TypeUnion }, type_function: struct { @@ -2241,17 +2490,22 @@ pub const Tag = enum(u8) { /// An opaque type. /// data is index of Key.OpaqueType in extra. type_opaque, - /// A struct type. - /// data is Module.Struct.OptionalIndex - /// The `none` tag is used to represent `@TypeOf(.{})`. + /// A non-packed struct type. + /// data is 0 or extra index of `TypeStruct`. + /// data == 0 represents `@TypeOf(.{})`. type_struct, - /// A struct type that has only a namespace; no fields, and there is no - /// Module.Struct object allocated for it. + /// A non-packed struct type that has only a namespace; no fields. /// data is Module.Namespace.Index. type_struct_ns, /// An AnonStructType which stores types, names, and values for fields. /// data is extra index of `TypeStructAnon`. type_struct_anon, + /// A packed struct, no fields have any init values. + /// data is extra index of `TypeStructPacked`. + type_struct_packed, + /// A packed struct, one or more fields have init values. + /// data is extra index of `TypeStructPacked`. + type_struct_packed_inits, /// An AnonStructType which has only types and values for fields. /// data is extra index of `TypeStructAnon`. type_tuple_anon, @@ -2461,9 +2715,10 @@ pub const Tag = enum(u8) { .type_enum_nonexhaustive => EnumExplicit, .simple_type => unreachable, .type_opaque => OpaqueType, - .type_struct => unreachable, + .type_struct => TypeStruct, .type_struct_ns => unreachable, .type_struct_anon => TypeStructAnon, + .type_struct_packed, .type_struct_packed_inits => TypeStructPacked, .type_tuple_anon => TypeStructAnon, .type_union => TypeUnion, .type_function => TypeFunction, @@ -2634,11 +2889,90 @@ pub const Tag = enum(u8) { any_aligned_fields: bool, layout: std.builtin.Type.ContainerLayout, status: UnionType.Status, - requires_comptime: UnionType.RequiresComptime, + requires_comptime: RequiresComptime, assumed_runtime_bits: bool, _: u21 = 0, }; }; + + /// Trailing: + /// 0. type: Index for each fields_len + /// 1. name: NullTerminatedString for each fields_len + /// 2. init: Index for each fields_len // if tag is type_struct_packed_inits + pub const TypeStructPacked = struct { + decl: Module.Decl.Index, + zir_index: Zir.Inst.Index, + fields_len: u32, + namespace: Module.Namespace.OptionalIndex, + backing_int_ty: Index, + names_map: MapIndex, + }; + + /// At first I thought of storing the denormalized data externally, such as... + /// + /// * runtime field order + /// * calculated field offsets + /// * size and alignment of the struct + /// + /// ...since these can be computed based on the other data here. However, + /// this data does need to be memoized, and therefore stored in memory + /// while the compiler is running, in order to avoid O(N^2) logic in many + /// places. Since the data can be stored compactly in the InternPool + /// representation, it is better for memory usage to store denormalized data + /// here, and potentially also better for performance as well. It's also simpler + /// than coming up with some other scheme for the data. + /// + /// Trailing: + /// 0. type: Index for each field in declared order + /// 1. if not is_tuple: + /// names_map: MapIndex, + /// name: NullTerminatedString // for each field in declared order + /// 2. if any_default_inits: + /// init: Index // for each field in declared order + /// 3. if has_namespace: + /// namespace: Module.Namespace.Index + /// 4. if any_aligned_fields: + /// align: Alignment // for each field in declared order + /// 5. if any_comptime_fields: + /// field_is_comptime_bits: u32 // minimal number of u32s needed, LSB is field 0 + /// 6. if has_reordered_fields: + /// field_index: RuntimeOrder // for each field in runtime order + /// 7. field_offset: u32 // for each field in declared order, undef until layout_resolved + pub const TypeStruct = struct { + decl: Module.Decl.Index, + zir_index: Zir.Inst.Index, + fields_len: u32, + flags: Flags, + size: u32, + + pub const Flags = packed struct(u32) { + has_runtime_order: bool, + is_extern: bool, + known_non_opv: bool, + requires_comptime: RequiresComptime, + is_tuple: bool, + assumed_runtime_bits: bool, + has_namespace: bool, + has_reordered_fields: bool, + any_comptime_fields: bool, + any_default_inits: bool, + any_aligned_fields: bool, + /// `undefined` until the layout_resolved + alignment: Alignment, + /// Dependency loop detection when resolving field types. + field_types_wip: bool, + /// Dependency loop detection when resolving struct layout. + layout_wip: bool, + /// Determines whether `size`, `alignment`, runtime field order, and + /// field offets are populated. + layout_resolved: bool, + // The types and all its fields have had their layout resolved. Even through pointer, + // which `layout_resolved` does not ensure. + fully_resolved: bool, + + _: u10 = 0, + }; + }; }; /// State that is mutable during semantic analysis. This data is not used for @@ -2764,20 +3098,26 @@ pub const SimpleValue = enum(u32) { /// Stored as a power-of-two, with one special value to indicate none. pub const Alignment = enum(u6) { + @"1" = 0, + @"2" = 1, + @"4" = 2, + @"8" = 3, + @"16" = 4, + @"32" = 5, none = std.math.maxInt(u6), _, pub fn toByteUnitsOptional(a: Alignment) ?u64 { return switch (a) { .none => null, - _ => @as(u64, 1) << @intFromEnum(a), + else => @as(u64, 1) << @intFromEnum(a), }; } pub fn toByteUnits(a: Alignment, default: u64) u64 { return switch (a) { .none => default, - _ => @as(u64, 1) << @intFromEnum(a), + else => @as(u64, 1) << @intFromEnum(a), }; } @@ -2792,11 +3132,65 @@ pub const Alignment = enum(u6) { return fromByteUnits(n); } + pub fn toLog2Units(a: Alignment) u6 { + assert(a != .none); + return @intFromEnum(a); + } + + /// This is just a glorified `@enumFromInt` but using it can help + /// document the intended conversion. + /// The parameter uses a u32 for convenience at the callsite. + pub fn fromLog2Units(a: u32) Alignment { + assert(a != @intFromEnum(Alignment.none)); + return @enumFromInt(a); + } + pub fn order(lhs: Alignment, rhs: Alignment) std.math.Order { - assert(lhs != .none and rhs != .none); + assert(lhs != .none); + assert(rhs != .none); return std.math.order(@intFromEnum(lhs), @intFromEnum(rhs)); } + pub fn compare(lhs: Alignment, op: std.math.CompareOperator, rhs: Alignment) bool { + assert(lhs != .none); + assert(rhs != .none); + return std.math.compare(@intFromEnum(lhs), op, @intFromEnum(rhs)); + } + + /// Treats `none` as zero. + pub fn max(lhs: Alignment, rhs: Alignment) Alignment { + if (lhs == .none) return rhs; + if (rhs == .none) return lhs; + return @enumFromInt(@max(@intFromEnum(lhs), @intFromEnum(rhs))); + } + + /// Treats `none` as maximum value. + pub fn min(lhs: Alignment, rhs: Alignment) Alignment { + if (lhs == .none) return rhs; + if (rhs == .none) return lhs; + return @enumFromInt(@min(@intFromEnum(lhs), @intFromEnum(rhs))); + } + + /// Align an address forwards to this alignment. + pub fn forward(a: Alignment, addr: u64) u64 { + assert(a != .none); + const x = (@as(u64, 1) << @intFromEnum(a)) - 1; + return (addr + x) & ~x; + } + + /// Align an address backwards to this alignment. + pub fn backward(a: Alignment, addr: u64) u64 { + assert(a != .none); + const x = (@as(u64, 1) << @intFromEnum(a)) - 1; + return addr & ~x; + } + + /// Check if an address is aligned to this amount. + pub fn check(a: Alignment, addr: u64) bool { + assert(a != .none); + return @ctz(addr) >= @intFromEnum(a); + } + /// An array of `Alignment` objects existing within the `extra` array. /// This type exists to provide a struct with lifetime that is /// not invalidated when items are added to the `InternPool`. @@ -2811,6 +3205,16 @@ pub const Alignment = enum(u6) { return @ptrCast(bytes[0..slice.len]); } }; + + const LlvmBuilderAlignment = @import("codegen/llvm/Builder.zig").Alignment; + + pub fn toLlvm(this: @This()) LlvmBuilderAlignment { + return @enumFromInt(@intFromEnum(this)); + } + + pub fn fromLlvm(other: LlvmBuilderAlignment) @This() { + return @enumFromInt(@intFromEnum(other)); + } }; /// Used for non-sentineled arrays that have length fitting in u32, as well as @@ -3065,9 +3469,6 @@ pub fn deinit(ip: *InternPool, gpa: Allocator) void { ip.limbs.deinit(gpa); ip.string_bytes.deinit(gpa); - ip.structs_free_list.deinit(gpa); - ip.allocated_structs.deinit(gpa); - ip.decls_free_list.deinit(gpa); ip.allocated_decls.deinit(gpa); @@ -3149,24 +3550,43 @@ pub fn indexToKey(ip: *const InternPool, index: Index) Key { }, .type_opaque => .{ .opaque_type = ip.extraData(Key.OpaqueType, data) }, - .type_struct => { - const struct_index: Module.Struct.OptionalIndex = @enumFromInt(data); - const namespace = if (struct_index.unwrap()) |i| - ip.structPtrConst(i).namespace.toOptional() - else - .none; - return .{ .struct_type = .{ - .index = struct_index, - .namespace = namespace, - } }; - }, + + .type_struct => .{ .struct_type = if (data == 0) .{ + .extra_index = 0, + .namespace = .none, + .decl = .none, + .zir_index = @as(u32, undefined), + .layout = .Auto, + .field_names = .{ .start = 0, .len = 0 }, + .field_types = .{ .start = 0, .len = 0 }, + .field_inits = .{ .start = 0, .len = 0 }, + .field_aligns = .{ .start = 0, .len = 0 }, + .runtime_order = .{ .start = 0, .len = 0 }, + .comptime_bits = .{ .start = 0, .len = 0 }, + .offsets = .{ .start = 0, .len = 0 }, + .names_map = undefined, + } else extraStructType(ip, data) }, + .type_struct_ns => .{ .struct_type = .{ - .index = .none, + .extra_index = 0, .namespace = @as(Module.Namespace.Index, @enumFromInt(data)).toOptional(), + .decl = .none, + .zir_index = @as(u32, undefined), + .layout = .Auto, + .field_names = .{ .start = 0, .len = 0 }, + .field_types = .{ .start = 0, .len = 0 }, + .field_inits = .{ .start = 0, .len = 0 }, + .field_aligns = .{ .start = 0, .len = 0 }, + .runtime_order = .{ .start = 0, .len = 0 }, + .comptime_bits = .{ .start = 0, .len = 0 }, + .offsets = .{ .start = 0, .len = 0 }, + .names_map = undefined, } }, .type_struct_anon => .{ .anon_struct_type = extraTypeStructAnon(ip, data) }, .type_tuple_anon => .{ .anon_struct_type = extraTypeTupleAnon(ip, data) }, + .type_struct_packed => .{ .struct_type = extraPackedStructType(ip, data, false) }, + .type_struct_packed_inits => .{ .struct_type = extraPackedStructType(ip, data, true) }, .type_union => .{ .union_type = extraUnionType(ip, data) }, .type_enum_auto => { @@ -3476,10 +3896,15 @@ pub fn indexToKey(ip: *const InternPool, index: Index) Key { const values = ip.extra.items[type_struct_anon.end + fields_len ..][0..fields_len]; return .{ .aggregate = .{ .ty = ty, - .storage = .{ .elems = @as([]const Index, @ptrCast(values)) }, + .storage = .{ .elems = @ptrCast(values) }, } }; }, + .type_struct_packed, .type_struct_packed_inits => { + // a packed struct has a 0-bit backing type + @panic("TODO"); + }, + .type_enum_auto, .type_enum_explicit, .type_union, @@ -3490,7 +3915,7 @@ pub fn indexToKey(ip: *const InternPool, index: Index) Key { }, .bytes => { const extra = ip.extraData(Bytes, data); - const len = @as(u32, @intCast(ip.aggregateTypeLenIncludingSentinel(extra.ty))); + const len: u32 = @intCast(ip.aggregateTypeLenIncludingSentinel(extra.ty)); return .{ .aggregate = .{ .ty = extra.ty, .storage = .{ .bytes = ip.string_bytes.items[@intFromEnum(extra.bytes)..][0..len] }, @@ -3498,8 +3923,8 @@ pub fn indexToKey(ip: *const InternPool, index: Index) Key { }, .aggregate => { const extra = ip.extraDataTrail(Tag.Aggregate, data); - const len = @as(u32, @intCast(ip.aggregateTypeLenIncludingSentinel(extra.data.ty))); - const fields = @as([]const Index, @ptrCast(ip.extra.items[extra.end..][0..len])); + const len: u32 = @intCast(ip.aggregateTypeLenIncludingSentinel(extra.data.ty)); + const fields: []const Index = @ptrCast(ip.extra.items[extra.end..][0..len]); return .{ .aggregate = .{ .ty = extra.data.ty, .storage = .{ .elems = fields }, @@ -3603,6 +4028,44 @@ fn extraTypeTupleAnon(ip: *const InternPool, extra_index: u32) Key.AnonStructTyp }; } +fn extraStructType(ip: *const InternPool, extra_index: u32) Key.StructType { + _ = ip; + _ = extra_index; + @panic("TODO"); +} + +fn extraPackedStructType(ip: *const InternPool, extra_index: u32, inits: bool) Key.StructType { + const type_struct_packed = ip.extraDataTrail(Tag.TypeStructPacked, extra_index); + const fields_len = type_struct_packed.data.fields_len; + return .{ + .extra_index = extra_index, + .decl = type_struct_packed.data.decl.toOptional(), + .namespace = type_struct_packed.data.namespace, + .zir_index = type_struct_packed.data.zir_index, + .layout = .Packed, + .field_types = .{ + .start = type_struct_packed.end, + .len = fields_len, + }, + .field_names = .{ + .start = type_struct_packed.end + fields_len, + .len = fields_len, + }, + .field_inits = if (inits) .{ + .start = type_struct_packed.end + fields_len + fields_len, + .len = fields_len, + } else .{ + .start = 0, + .len = 0, + }, + .field_aligns = .{ .start = 0, .len = 0 }, + .runtime_order = .{ .start = 0, .len = 0 }, + .comptime_bits = .{ .start = 0, .len = 0 }, + .offsets = .{ .start = 0, .len = 0 }, + .names_map = type_struct_packed.data.names_map, + }; +} + fn extraFuncType(ip: *const InternPool, extra_index: u32) Key.FuncType { const type_function = ip.extraDataTrail(Tag.TypeFunction, extra_index); var index: usize = type_function.end; @@ -3831,8 +4294,9 @@ pub fn get(ip: *InternPool, gpa: Allocator, key: Key) Allocator.Error!Index { .error_set_type => |error_set_type| { assert(error_set_type.names_map == .none); assert(std.sort.isSorted(NullTerminatedString, error_set_type.names.get(ip), {}, NullTerminatedString.indexLessThan)); - const names_map = try ip.addMap(gpa); - try addStringsToMap(ip, gpa, names_map, error_set_type.names.get(ip)); + const names = error_set_type.names.get(ip); + const names_map = try ip.addMap(gpa, names.len); + addStringsToMap(ip, names_map, names); const names_len = error_set_type.names.len; try ip.extra.ensureUnusedCapacity(gpa, @typeInfo(Tag.ErrorSet).Struct.fields.len + names_len); ip.items.appendAssumeCapacity(.{ @@ -3877,21 +4341,8 @@ pub fn get(ip: *InternPool, gpa: Allocator, key: Key) Allocator.Error!Index { }); }, - .struct_type => |struct_type| { - ip.items.appendAssumeCapacity(if (struct_type.index.unwrap()) |i| .{ - .tag = .type_struct, - .data = @intFromEnum(i), - } else if (struct_type.namespace.unwrap()) |i| .{ - .tag = .type_struct_ns, - .data = @intFromEnum(i), - } else .{ - .tag = .type_struct, - .data = @intFromEnum(Module.Struct.OptionalIndex.none), - }); - }, - + .struct_type => unreachable, // use getStructType() instead .anon_struct_type => unreachable, // use getAnonStructType() instead - .union_type => unreachable, // use getUnionType() instead .opaque_type => |opaque_type| { @@ -3994,7 +4445,7 @@ pub fn get(ip: *InternPool, gpa: Allocator, key: Key) Allocator.Error!Index { }, .struct_type => |struct_type| { assert(ptr.addr == .field); - assert(base_index.index < ip.structPtrUnwrapConst(struct_type.index).?.fields.count()); + assert(base_index.index < struct_type.field_types.len); }, .union_type => |union_key| { const union_type = ip.loadUnionType(union_key); @@ -4388,12 +4839,9 @@ pub fn get(ip: *InternPool, gpa: Allocator, key: Key) Allocator.Error!Index { assert(ip.typeOf(elem) == child); } }, - .struct_type => |struct_type| { - for ( - aggregate.storage.values(), - ip.structPtrUnwrapConst(struct_type.index).?.fields.values(), - ) |elem, field| { - assert(ip.typeOf(elem) == field.ty.toIntern()); + .struct_type => |t| { + for (aggregate.storage.values(), t.field_types.get(ip)) |elem, field_ty| { + assert(ip.typeOf(elem) == field_ty); } }, .anon_struct_type => |anon_struct_type| { @@ -4635,6 +5083,28 @@ pub fn getUnionType(ip: *InternPool, gpa: Allocator, ini: UnionTypeInit) Allocat return @enumFromInt(ip.items.len - 1); } +pub const StructTypeInit = struct { + decl: Module.Decl.Index, + namespace: Module.Namespace.OptionalIndex, + layout: std.builtin.Type.ContainerLayout, + zir_index: Zir.Inst.Index, + fields_len: u32, + known_non_opv: bool, + requires_comptime: RequiresComptime, + is_tuple: bool, +}; + +pub fn getStructType( + ip: *InternPool, + gpa: Allocator, + ini: StructTypeInit, +) Allocator.Error!Index { + _ = ip; + _ = gpa; + _ = ini; + @panic("TODO"); +} + pub const AnonStructTypeInit = struct { types: []const Index, /// This may be empty, indicating this is a tuple. @@ -4997,10 +5467,10 @@ pub fn getErrorSetType( }); errdefer ip.items.len -= 1; - const names_map = try ip.addMap(gpa); + const names_map = try ip.addMap(gpa, names.len); errdefer _ = ip.maps.pop(); - try addStringsToMap(ip, gpa, names_map, names); + addStringsToMap(ip, names_map, names); return @enumFromInt(ip.items.len - 1); } @@ -5299,19 +5769,9 @@ pub const IncompleteEnumType = struct { pub fn addFieldName( self: @This(), ip: *InternPool, - gpa: Allocator, name: NullTerminatedString, - ) Allocator.Error!?u32 { - const map = &ip.maps.items[@intFromEnum(self.names_map)]; - const field_index = map.count(); - const strings = ip.extra.items[self.names_start..][0..field_index]; - const adapter: NullTerminatedString.Adapter = .{ - .strings = @as([]const NullTerminatedString, @ptrCast(strings)), - }; - const gop = try map.getOrPutAdapted(gpa, name, adapter); - if (gop.found_existing) return @intCast(gop.index); - ip.extra.items[self.names_start + field_index] = @intFromEnum(name); - return null; + ) ?u32 { + return ip.addFieldName(self.names_map, self.names_start, name); } /// Returns the already-existing field with the same value, if any. @@ -5319,17 +5779,14 @@ pub const IncompleteEnumType = struct { pub fn addFieldValue( self: @This(), ip: *InternPool, - gpa: Allocator, value: Index, - ) Allocator.Error!?u32 { + ) ?u32 { assert(ip.typeOf(value) == @as(Index, @enumFromInt(ip.extra.items[self.tag_ty_index]))); const map = &ip.maps.items[@intFromEnum(self.values_map.unwrap().?)]; const field_index = map.count(); const indexes = ip.extra.items[self.values_start..][0..field_index]; - const adapter: Index.Adapter = .{ - .indexes = @as([]const Index, @ptrCast(indexes)), - }; - const gop = try map.getOrPutAdapted(gpa, value, adapter); + const adapter: Index.Adapter = .{ .indexes = @ptrCast(indexes) }; + const gop = map.getOrPutAssumeCapacityAdapted(value, adapter); if (gop.found_existing) return @intCast(gop.index); ip.extra.items[self.values_start + field_index] = @intFromEnum(value); return null; @@ -5370,7 +5827,7 @@ fn getIncompleteEnumAuto( const gop = try ip.map.getOrPutAdapted(gpa, enum_type.toKey(), adapter); assert(!gop.found_existing); - const names_map = try ip.addMap(gpa); + const names_map = try ip.addMap(gpa, enum_type.fields_len); const extra_fields_len: u32 = @typeInfo(EnumAuto).Struct.fields.len; try ip.extra.ensureUnusedCapacity(gpa, extra_fields_len + enum_type.fields_len); @@ -5390,7 +5847,7 @@ fn getIncompleteEnumAuto( }); ip.extra.appendNTimesAssumeCapacity(@intFromEnum(Index.none), enum_type.fields_len); return .{ - .index = @as(Index, @enumFromInt(ip.items.len - 1)), + .index = @enumFromInt(ip.items.len - 1), .tag_ty_index = extra_index + std.meta.fieldIndex(EnumAuto, "int_tag_type").?, .names_map = names_map, .names_start = extra_index + extra_fields_len, @@ -5412,9 +5869,9 @@ fn getIncompleteEnumExplicit( const gop = try ip.map.getOrPutAdapted(gpa, enum_type.toKey(), adapter); assert(!gop.found_existing); - const names_map = try ip.addMap(gpa); + const names_map = try ip.addMap(gpa, enum_type.fields_len); const values_map: OptionalMapIndex = if (!enum_type.has_values) .none else m: { - const values_map = try ip.addMap(gpa); + const values_map = try ip.addMap(gpa, enum_type.fields_len); break :m values_map.toOptional(); }; @@ -5441,7 +5898,7 @@ fn getIncompleteEnumExplicit( // This is both fields and values (if present). ip.extra.appendNTimesAssumeCapacity(@intFromEnum(Index.none), reserved_len); return .{ - .index = @as(Index, @enumFromInt(ip.items.len - 1)), + .index = @enumFromInt(ip.items.len - 1), .tag_ty_index = extra_index + std.meta.fieldIndex(EnumExplicit, "int_tag_type").?, .names_map = names_map, .names_start = extra_index + extra_fields_len, @@ -5484,8 +5941,8 @@ pub fn getEnum(ip: *InternPool, gpa: Allocator, ini: GetEnumInit) Allocator.Erro switch (ini.tag_mode) { .auto => { - const names_map = try ip.addMap(gpa); - try addStringsToMap(ip, gpa, names_map, ini.names); + const names_map = try ip.addMap(gpa, ini.names.len); + addStringsToMap(ip, names_map, ini.names); const fields_len: u32 = @intCast(ini.names.len); try ip.extra.ensureUnusedCapacity(gpa, @typeInfo(EnumAuto).Struct.fields.len + @@ -5514,12 +5971,12 @@ pub fn finishGetEnum( ini: GetEnumInit, tag: Tag, ) Allocator.Error!Index { - const names_map = try ip.addMap(gpa); - try addStringsToMap(ip, gpa, names_map, ini.names); + const names_map = try ip.addMap(gpa, ini.names.len); + addStringsToMap(ip, names_map, ini.names); const values_map: OptionalMapIndex = if (ini.values.len == 0) .none else m: { - const values_map = try ip.addMap(gpa); - try addIndexesToMap(ip, gpa, values_map, ini.values); + const values_map = try ip.addMap(gpa, ini.values.len); + addIndexesToMap(ip, values_map, ini.values); break :m values_map.toOptional(); }; const fields_len: u32 = @intCast(ini.names.len); @@ -5553,35 +6010,35 @@ pub fn getAssumeExists(ip: *const InternPool, key: Key) Index { fn addStringsToMap( ip: *InternPool, - gpa: Allocator, map_index: MapIndex, strings: []const NullTerminatedString, -) Allocator.Error!void { +) void { const map = &ip.maps.items[@intFromEnum(map_index)]; const adapter: NullTerminatedString.Adapter = .{ .strings = strings }; for (strings) |string| { - const gop = try map.getOrPutAdapted(gpa, string, adapter); + const gop = map.getOrPutAssumeCapacityAdapted(string, adapter); assert(!gop.found_existing); } } fn addIndexesToMap( ip: *InternPool, - gpa: Allocator, map_index: MapIndex, indexes: []const Index, -) Allocator.Error!void { +) void { const map = &ip.maps.items[@intFromEnum(map_index)]; const adapter: Index.Adapter = .{ .indexes = indexes }; for (indexes) |index| { - const gop = try map.getOrPutAdapted(gpa, index, adapter); + const gop = map.getOrPutAssumeCapacityAdapted(index, adapter); assert(!gop.found_existing); } } -fn addMap(ip: *InternPool, gpa: Allocator) Allocator.Error!MapIndex { +fn addMap(ip: *InternPool, gpa: Allocator, cap: usize) Allocator.Error!MapIndex { const ptr = try ip.maps.addOne(gpa); + errdefer _ = ip.maps.pop(); ptr.* = .{}; + try ptr.ensureTotalCapacity(gpa, cap); return @enumFromInt(ip.maps.items.len - 1); } @@ -5632,8 +6089,9 @@ fn addExtraAssumeCapacity(ip: *InternPool, extra: anytype) u32 { Tag.TypePointer.Flags, Tag.TypeFunction.Flags, Tag.TypePointer.PackedOffset, - Tag.Variable.Flags, Tag.TypeUnion.Flags, + Tag.TypeStruct.Flags, + Tag.Variable.Flags, => @bitCast(@field(extra, field.name)), else => @compileError("bad field type: " ++ @typeName(field.type)), @@ -5705,6 +6163,7 @@ fn extraDataTrail(ip: *const InternPool, comptime T: type, index: usize) struct Tag.TypeFunction.Flags, Tag.TypePointer.PackedOffset, Tag.TypeUnion.Flags, + Tag.TypeStruct.Flags, Tag.Variable.Flags, FuncAnalysis, => @bitCast(int32), @@ -6093,8 +6552,7 @@ pub fn getCoerced(ip: *InternPool, gpa: Allocator, val: Index, new_ty: Index) Al const new_elem_ty = switch (ip.indexToKey(new_ty)) { inline .array_type, .vector_type => |seq_type| seq_type.child, .anon_struct_type => |anon_struct_type| anon_struct_type.types.get(ip)[i], - .struct_type => |struct_type| ip.structPtr(struct_type.index.unwrap().?) - .fields.values()[i].ty.toIntern(), + .struct_type => |struct_type| struct_type.field_types.get(ip)[i], else => unreachable, }; elem.* = try ip.getCoerced(gpa, elem.*, new_elem_ty); @@ -6206,25 +6664,6 @@ pub fn getCoercedInts(ip: *InternPool, gpa: Allocator, int: Key.Int, new_ty: Ind } }); } -pub fn indexToStructType(ip: *const InternPool, val: Index) Module.Struct.OptionalIndex { - assert(val != .none); - const tags = ip.items.items(.tag); - if (tags[@intFromEnum(val)] != .type_struct) return .none; - const datas = ip.items.items(.data); - return @as(Module.Struct.Index, @enumFromInt(datas[@intFromEnum(val)])).toOptional(); -} - -pub fn indexToUnionType(ip: *const InternPool, val: Index) Module.Union.OptionalIndex { - assert(val != .none); - const tags = ip.items.items(.tag); - switch (tags[@intFromEnum(val)]) { - .type_union => {}, - else => return .none, - } - const datas = ip.items.items(.data); - return @as(Module.Union.Index, @enumFromInt(datas[@intFromEnum(val)])).toOptional(); -} - pub fn indexToFuncType(ip: *const InternPool, val: Index) ?Key.FuncType { assert(val != .none); const tags = ip.items.items(.tag); @@ -6337,20 +6776,16 @@ fn dumpStatsFallible(ip: *const InternPool, arena: Allocator) anyerror!void { const items_size = (1 + 4) * ip.items.len; const extra_size = 4 * ip.extra.items.len; const limbs_size = 8 * ip.limbs.items.len; - // TODO: fields size is not taken into account - const structs_size = ip.allocated_structs.len * - (@sizeOf(Module.Struct) + @sizeOf(Module.Namespace)); const decls_size = ip.allocated_decls.len * @sizeOf(Module.Decl); // TODO: map overhead size is not taken into account - const total_size = @sizeOf(InternPool) + items_size + extra_size + limbs_size + structs_size + decls_size; + const total_size = @sizeOf(InternPool) + items_size + extra_size + limbs_size + decls_size; std.debug.print( \\InternPool size: {d} bytes \\ {d} items: {d} bytes \\ {d} extra: {d} bytes \\ {d} limbs: {d} bytes - \\ {d} structs: {d} bytes \\ {d} decls: {d} bytes \\ , .{ @@ -6361,8 +6796,6 @@ fn dumpStatsFallible(ip: *const InternPool, arena: Allocator) anyerror!void { extra_size, ip.limbs.items.len, limbs_size, - ip.allocated_structs.len, - structs_size, ip.allocated_decls.len, decls_size, }); @@ -6399,17 +6832,40 @@ fn dumpStatsFallible(ip: *const InternPool, arena: Allocator) anyerror!void { .type_enum_auto => @sizeOf(EnumAuto), .type_opaque => @sizeOf(Key.OpaqueType), .type_struct => b: { - const struct_index = @as(Module.Struct.Index, @enumFromInt(data)); - const struct_obj = ip.structPtrConst(struct_index); - break :b @sizeOf(Module.Struct) + - @sizeOf(Module.Namespace) + - (struct_obj.fields.count() * @sizeOf(Module.Struct.Field)); + const info = ip.extraData(Tag.TypeStruct, data); + var ints: usize = @typeInfo(Tag.TypeStruct).Struct.fields.len; + ints += info.fields_len; // types + if (!info.flags.is_tuple) { + ints += 1; // names_map + ints += info.fields_len; // names + } + if (info.flags.any_default_inits) + ints += info.fields_len; // inits + ints += @intFromBool(info.flags.has_namespace); // namespace + if (info.flags.any_aligned_fields) + ints += (info.fields_len + 3) / 4; // aligns + if (info.flags.any_comptime_fields) + ints += (info.fields_len + 31) / 32; // comptime bits + if (info.flags.has_reordered_fields) + ints += info.fields_len; // runtime order + ints += info.fields_len; // offsets + break :b @sizeOf(u32) * ints; }, .type_struct_ns => @sizeOf(Module.Namespace), .type_struct_anon => b: { const info = ip.extraData(TypeStructAnon, data); break :b @sizeOf(TypeStructAnon) + (@sizeOf(u32) * 3 * info.fields_len); }, + .type_struct_packed => b: { + const info = ip.extraData(Tag.TypeStructPacked, data); + break :b @sizeOf(u32) * (@typeInfo(Tag.TypeStructPacked).Struct.fields.len + + info.fields_len + info.fields_len); + }, + .type_struct_packed_inits => b: { + const info = ip.extraData(Tag.TypeStructPacked, data); + break :b @sizeOf(u32) * (@typeInfo(Tag.TypeStructPacked).Struct.fields.len + + info.fields_len + info.fields_len + info.fields_len); + }, .type_tuple_anon => b: { const info = ip.extraData(TypeStructAnon, data); break :b @sizeOf(TypeStructAnon) + (@sizeOf(u32) * 2 * info.fields_len); @@ -6562,6 +7018,8 @@ fn dumpAllFallible(ip: *const InternPool) anyerror!void { .type_struct, .type_struct_ns, .type_struct_anon, + .type_struct_packed, + .type_struct_packed_inits, .type_tuple_anon, .type_union, .type_function, @@ -6677,18 +7135,6 @@ pub fn dumpGenericInstancesFallible(ip: *const InternPool, allocator: Allocator) try bw.flush(); } -pub fn structPtr(ip: *InternPool, index: Module.Struct.Index) *Module.Struct { - return ip.allocated_structs.at(@intFromEnum(index)); -} - -pub fn structPtrConst(ip: *const InternPool, index: Module.Struct.Index) *const Module.Struct { - return ip.allocated_structs.at(@intFromEnum(index)); -} - -pub fn structPtrUnwrapConst(ip: *const InternPool, index: Module.Struct.OptionalIndex) ?*const Module.Struct { - return structPtrConst(ip, index.unwrap() orelse return null); -} - pub fn declPtr(ip: *InternPool, index: Module.Decl.Index) *Module.Decl { return ip.allocated_decls.at(@intFromEnum(index)); } @@ -6701,28 +7147,6 @@ pub fn namespacePtr(ip: *InternPool, index: Module.Namespace.Index) *Module.Name return ip.allocated_namespaces.at(@intFromEnum(index)); } -pub fn createStruct( - ip: *InternPool, - gpa: Allocator, - initialization: Module.Struct, -) Allocator.Error!Module.Struct.Index { - if (ip.structs_free_list.popOrNull()) |index| { - ip.allocated_structs.at(@intFromEnum(index)).* = initialization; - return index; - } - const ptr = try ip.allocated_structs.addOne(gpa); - ptr.* = initialization; - return @enumFromInt(ip.allocated_structs.len - 1); -} - -pub fn destroyStruct(ip: *InternPool, gpa: Allocator, index: Module.Struct.Index) void { - ip.structPtr(index).* = undefined; - ip.structs_free_list.append(gpa, index) catch { - // In order to keep `destroyStruct` a non-fallible function, we ignore memory - // allocation failures here, instead leaking the Struct until garbage collection. - }; -} - pub fn createDecl( ip: *InternPool, gpa: Allocator, @@ -6967,6 +7391,8 @@ pub fn typeOf(ip: *const InternPool, index: Index) Index { .type_struct, .type_struct_ns, .type_struct_anon, + .type_struct_packed, + .type_struct_packed_inits, .type_tuple_anon, .type_union, .type_function, @@ -7056,7 +7482,7 @@ pub fn toEnum(ip: *const InternPool, comptime E: type, i: Index) E { pub fn aggregateTypeLen(ip: *const InternPool, ty: Index) u64 { return switch (ip.indexToKey(ty)) { - .struct_type => |struct_type| ip.structPtrConst(struct_type.index.unwrap() orelse return 0).fields.count(), + .struct_type => |struct_type| struct_type.field_types.len, .anon_struct_type => |anon_struct_type| anon_struct_type.types.len, .array_type => |array_type| array_type.len, .vector_type => |vector_type| vector_type.len, @@ -7066,7 +7492,7 @@ pub fn aggregateTypeLen(ip: *const InternPool, ty: Index) u64 { pub fn aggregateTypeLenIncludingSentinel(ip: *const InternPool, ty: Index) u64 { return switch (ip.indexToKey(ty)) { - .struct_type => |struct_type| ip.structPtrConst(struct_type.index.unwrap() orelse return 0).fields.count(), + .struct_type => |struct_type| struct_type.field_types.len, .anon_struct_type => |anon_struct_type| anon_struct_type.types.len, .array_type => |array_type| array_type.len + @intFromBool(array_type.sentinel != .none), .vector_type => |vector_type| vector_type.len, @@ -7301,6 +7727,8 @@ pub fn zigTypeTagOrPoison(ip: *const InternPool, index: Index) error{GenericPois .type_struct, .type_struct_ns, .type_struct_anon, + .type_struct_packed, + .type_struct_packed_inits, .type_tuple_anon, => .Struct, @@ -7526,6 +7954,40 @@ pub fn resolveBuiltinType(ip: *InternPool, want_index: Index, resolved_index: In .data = @intFromEnum(SimpleValue.@"unreachable"), }); } else { - // TODO: add the index to a free-list for reuse + // Here we could add the index to a free-list for reuse, but since + // there is so little garbage created this way it's not worth it. } } + +pub fn anonStructFieldTypes(ip: *const InternPool, i: Index) []const Index { + return ip.indexToKey(i).anon_struct_type.types; +} + +pub fn anonStructFieldsLen(ip: *const InternPool, i: Index) u32 { + return @intCast(ip.indexToKey(i).anon_struct_type.types.len); +} + +/// Asserts the type is a struct. +pub fn structDecl(ip: *const InternPool, i: Index) Module.Decl.OptionalIndex { + return switch (ip.indexToKey(i)) { + .struct_type => |t| t.decl, + else => unreachable, + }; +} + +/// Returns the already-existing field with the same name, if any. +pub fn addFieldName( + ip: *InternPool, + names_map: MapIndex, + names_start: u32, + name: NullTerminatedString, +) ?u32 { + const map = &ip.maps.items[@intFromEnum(names_map)]; + const field_index = map.count(); + const strings = ip.extra.items[names_start..][0..field_index]; + const adapter: NullTerminatedString.Adapter = .{ .strings = @ptrCast(strings) }; + const gop = map.getOrPutAssumeCapacityAdapted(name, adapter); + if (gop.found_existing) return @intCast(gop.index); + ip.extra.items[names_start + field_index] = @intFromEnum(name); + return null; +} -- cgit v1.2.3