diff --git a/src/InternPool.zig b/src/InternPool.zig index 86dd29c90a..36311100da 100644 --- a/src/InternPool.zig +++ b/src/InternPool.zig @@ -344,6 +344,7 @@ const KeyAdapter = struct { pub fn eql(ctx: @This(), a: Key, b_void: void, b_map_index: usize) bool { _ = b_void; + if (ctx.intern_pool.items.items(.tag)[b_map_index] == .removed) return false; return ctx.intern_pool.indexToKey(@as(Index, @enumFromInt(b_map_index))).eql(a, ctx.intern_pool); } @@ -551,14 +552,14 @@ pub const Key = union(enum) { /// This represents a struct that has been explicitly declared in source code, /// or was created with `@Type`. It is unique and based on a declaration. /// It may be a tuple, if declared like this: `struct {A, B, C}`. - struct_type: StructType, + struct_type: NamespaceType, /// This is an anonymous struct or tuple type which has no corresponding /// declaration. It is used for types that have no `struct` keyword in the /// source code, and were not created via `@Type`. anon_struct_type: AnonStructType, - union_type: Key.UnionType, - opaque_type: OpaqueType, - enum_type: EnumType, + union_type: NamespaceType, + opaque_type: NamespaceType, + enum_type: NamespaceType, func_type: FuncType, error_set_type: ErrorSetType, /// The payload is the function body, either a `func_decl` or `func_instance`. @@ -703,66 +704,41 @@ pub const Key = union(enum) { } }; - /// This is the hashmap key. To fetch other data associated with the struct, see `loadStructType`. - pub const StructType = struct { - /// The struct's owner Decl. `none` when the struct is `@TypeOf(.{})`. - decl: OptionalDeclIndex, - }; - - /// This is the hashmap key. To fetch other data associated with the opaque, see `loadOpaqueType`. - pub const OpaqueType = struct { - /// The opaque's owner Decl. - decl: DeclIndex, - }; - - /// This is the hashmap key. To fetch other data associated with the union, see `loadUnionType`. - pub const UnionType = struct { - /// The union's owner Decl. - decl: DeclIndex, - }; - - /// This is the hashmap key. To fetch other data associated with the enum, see `loadEnumType`. - pub const EnumType = struct { - /// The enum's owner Decl. - decl: DeclIndex, - }; - - pub const IncompleteEnumType = struct { - /// Same as corresponding `EnumType` field. - decl: DeclIndex, - /// Same as corresponding `EnumType` field. - namespace: OptionalNamespaceIndex, - /// The field names and field values are not known yet, but - /// the number of fields must be known ahead of time. - fields_len: u32, - /// This information is needed so that the size does not change - /// later when populating field values. - has_values: bool, - /// Same as corresponding `EnumType` field. - tag_mode: LoadedEnumType.TagMode, - /// This may be updated via `setTagType` later. - tag_ty: Index = .none, - zir_index: TrackedInst.Index.Optional, - captures: []const CaptureValue, - - pub fn toEnumType(self: @This()) LoadedEnumType { - if (true) @compileError("AHHHH"); - return .{ - .decl = self.decl, - .namespace = self.namespace, - .tag_ty = self.tag_ty, - .tag_mode = self.tag_mode, - .names = .{ .start = 0, .len = 0 }, - .values = .{ .start = 0, .len = 0 }, - .zir_index = self.zir_index, - }; - } - - /// Only the decl is used for hashing and equality, so we can construct - /// this minimal key for use with `map`. - pub fn toKey(self: @This()) Key { - return .{ .enum_type = .{ .decl = self.decl } }; - } + /// This is the hashmap key. To fetch other data associated with the type, see: + /// * `loadStructType` + /// * `loadUnionType` + /// * `loadEnumType` + /// * `loadOpaqueType` + pub const NamespaceType = union(enum) { + /// This type corresponds to an actual source declaration, e.g. `struct { ... }`. + /// It is hashed based on its ZIR instruction index and set of captures. + declared: struct { + /// A `struct_decl`, `union_decl`, `enum_decl`, or `opaque_decl` instruction. + zir_index: TrackedInst.Index, + /// The captured values of this type. These values must be fully resolved per the language spec. + captures: union(enum) { + owned: CaptureValue.Slice, + external: []const CaptureValue, + }, + }, + /// This type is an automatically-generated enum tag type for a union. + /// It is hashed based on the index of the union type it corresponds to. + generated_tag: struct { + /// The union for which this is a tag type. + union_type: Index, + }, + /// This type originates from a reification via `@Type`. + /// It is hased based on its ZIR instruction index and fields, attributes, etc. + /// To avoid making this key overly complex, the type-specific data is hased by Sema. + reified: struct { + /// A `reify` instruction. + zir_index: TrackedInst.Index, + /// A hash of this type's attributes, fields, etc, generated by Sema. + type_hash: u64, + }, + /// This type is `@TypeOf(.{})`. + /// TODO: can we change the language spec to not special-case this type? + empty_struct: void, }; pub const FuncType = struct { @@ -1113,12 +1089,37 @@ pub const Key = union(enum) { .payload => |y| Hash.hash(seed + 1, asBytes(&x.ty) ++ asBytes(&y)), }, - inline .opaque_type, + .variable => |variable| Hash.hash(seed, asBytes(&variable.decl)), + + .opaque_type, .enum_type, - .variable, .union_type, .struct_type, - => |x| Hash.hash(seed, asBytes(&x.decl)), + => |namespace_type| { + var hasher = Hash.init(seed); + std.hash.autoHash(&hasher, std.meta.activeTag(namespace_type)); + switch (namespace_type) { + .declared => |declared| { + std.hash.autoHash(&hasher, declared.zir_index); + const captures = switch (declared.captures) { + .owned => |cvs| cvs.get(ip), + .external => |cvs| cvs, + }; + for (captures) |cv| { + std.hash.autoHash(&hasher, cv); + } + }, + .generated_tag => |generated_tag| { + std.hash.autoHash(&hasher, generated_tag.union_type); + }, + .reified => |reified| { + std.hash.autoHash(&hasher, reified.zir_index); + std.hash.autoHash(&hasher, reified.type_hash); + }, + .empty_struct => {}, + } + return hasher.final(); + }, .int => |int| { var hasher = Hash.init(seed); @@ -1523,21 +1524,31 @@ pub const Key = union(enum) { } }, - .opaque_type => |a_info| { - const b_info = b.opaque_type; - return a_info.decl == b_info.decl; - }, - .enum_type => |a_info| { - const b_info = b.enum_type; - return a_info.decl == b_info.decl; - }, - .union_type => |a_info| { - 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; + inline .opaque_type, .enum_type, .union_type, .struct_type => |a_info, a_tag_ct| { + const b_info = @field(b, @tagName(a_tag_ct)); + if (std.meta.activeTag(a_info) != b_info) return false; + switch (a_info) { + .declared => |a_d| { + const b_d = b_info.declared; + if (a_d.zir_index != b_d.zir_index) return false; + const a_captures = switch (a_d.captures) { + .owned => |s| s.get(ip), + .external => |cvs| cvs, + }; + const b_captures = switch (b_d.captures) { + .owned => |s| s.get(ip), + .external => |cvs| cvs, + }; + return std.mem.eql(u32, @ptrCast(a_captures), @ptrCast(b_captures)); + }, + .generated_tag => |a_gt| return a_gt.union_type == b_info.generated_tag.union_type, + .reified => |a_r| { + const b_r = b_info.reified; + return a_r.zir_index == b_r.zir_index and + a_r.type_hash == b_r.type_hash; + }, + .empty_struct => return true, + } }, .aggregate => |a_info| { const b_info = b.aggregate; @@ -1685,7 +1696,7 @@ pub const LoadedUnionType = struct { /// The Decl that corresponds to the union itself. decl: DeclIndex, /// Represents the declarations inside this union. - namespace: NamespaceIndex, + namespace: OptionalNamespaceIndex, /// The enum tag type. enum_tag_ty: Index, /// List of field types in declaration order. @@ -1695,8 +1706,8 @@ pub const LoadedUnionType = struct { /// `none` means the ABI alignment of the type. /// If this slice has length 0 it means all elements are `none`. field_aligns: Alignment.Slice, - /// Index of the union_decl ZIR instruction. - zir_index: TrackedInst.Index.Optional, + /// Index of the union_decl or reify ZIR instruction. + zir_index: TrackedInst.Index, captures: CaptureValue.Slice, pub const RuntimeTag = enum(u2) { @@ -1845,6 +1856,9 @@ pub fn loadUnionType(ip: *const InternPool, index: Index) LoadedUnionType { .len = captures_len, }; extra_index += captures_len; + if (type_union.data.flags.is_reified) { + extra_index += 2; // PackedU64 + } const field_types: Index.Slice = .{ .start = extra_index, @@ -1880,7 +1894,8 @@ pub const LoadedStructType = struct { decl: OptionalDeclIndex, /// `none` when the struct has no declarations. namespace: OptionalNamespaceIndex, - /// Index of the `struct_decl` ZIR instruction. + /// Index of the `struct_decl` or `reify` ZIR instruction. + /// Only `none` when the struct is `@TypeOf(.{})`. zir_index: TrackedInst.Index.Optional, layout: std.builtin.Type.ContainerLayout, field_names: NullTerminatedString.Slice, @@ -2239,6 +2254,9 @@ pub fn loadStructType(ip: *const InternPool, index: Index) LoadedStructType { .len = captures_len, }; extra_index += captures_len; + if (extra.data.flags.is_reified) { + extra_index += 2; // PackedU64 + } const field_types: Index.Slice = .{ .start = extra_index, .len = fields_len, @@ -2286,7 +2304,7 @@ pub fn loadStructType(ip: *const InternPool, index: Index) LoadedStructType { .extra_index = item.data, .decl = extra.data.decl.toOptional(), .namespace = namespace, - .zir_index = extra.data.zir_index, + .zir_index = extra.data.zir_index.toOptional(), .layout = if (extra.data.flags.is_extern) .Extern else .Auto, .field_names = names, .field_types = field_types, @@ -2314,6 +2332,9 @@ pub fn loadStructType(ip: *const InternPool, index: Index) LoadedStructType { .len = captures_len, }; extra_index += captures_len; + if (extra.data.flags.is_reified) { + extra_index += 2; // PackedU64 + } const field_types: Index.Slice = .{ .start = extra_index, .len = fields_len, @@ -2336,7 +2357,7 @@ pub fn loadStructType(ip: *const InternPool, index: Index) LoadedStructType { .extra_index = item.data, .decl = extra.data.decl.toOptional(), .namespace = extra.data.namespace, - .zir_index = extra.data.zir_index, + .zir_index = extra.data.zir_index.toOptional(), .layout = .Packed, .field_names = field_names, .field_types = field_types, @@ -2372,6 +2393,7 @@ const LoadedEnumType = struct { names_map: MapIndex, /// This is guaranteed to not be `.none` if explicit values are provided. values_map: OptionalMapIndex, + /// This is `none` only if this is a generated tag type. zir_index: TrackedInst.Index.Optional, captures: CaptureValue.Slice, @@ -2425,15 +2447,23 @@ const LoadedEnumType = struct { pub fn loadEnumType(ip: *const InternPool, index: Index) LoadedEnumType { const item = ip.items.get(@intFromEnum(index)); - switch (item.tag) { + const tag_mode: LoadedEnumType.TagMode = switch (item.tag) { .type_enum_auto => { const extra = ip.extraDataTrail(EnumAuto, item.data); + var extra_index: u32 = @intCast(extra.end); + if (extra.data.zir_index == .none) { + extra_index += 1; // owner_union + } + const captures_len = if (extra.data.captures_len == std.math.maxInt(u32)) c: { + extra_index += 2; // type_hash: PackedU64 + break :c 0; + } else extra.data.captures_len; return .{ .decl = extra.data.decl, .namespace = extra.data.namespace, .tag_ty = extra.data.int_tag_type, .names = .{ - .start = @intCast(extra.end + extra.data.captures_len), + .start = extra_index + captures_len, .len = extra.data.fields_len, }, .values = .{ .start = 0, .len = 0 }, @@ -2442,41 +2472,45 @@ pub fn loadEnumType(ip: *const InternPool, index: Index) LoadedEnumType { .values_map = .none, .zir_index = extra.data.zir_index, .captures = .{ - .start = @intCast(extra.end), - .len = extra.data.captures_len, - }, - }; - }, - .type_enum_explicit, .type_enum_nonexhaustive => { - const extra = ip.extraDataTrail(EnumExplicit, item.data); - return .{ - .decl = extra.data.decl, - .namespace = extra.data.namespace, - .tag_ty = extra.data.int_tag_type, - .names = .{ - .start = @intCast(extra.end + extra.data.captures_len), - .len = extra.data.fields_len, - }, - .values = .{ - .start = @intCast(extra.end + extra.data.captures_len + extra.data.fields_len), - .len = if (extra.data.values_map != .none) extra.data.fields_len else 0, - }, - .tag_mode = switch (item.tag) { - .type_enum_explicit => .explicit, - .type_enum_nonexhaustive => .nonexhaustive, - else => unreachable, - }, - .names_map = extra.data.names_map, - .values_map = extra.data.values_map, - .zir_index = extra.data.zir_index, - .captures = .{ - .start = @intCast(extra.end), - .len = extra.data.captures_len, + .start = extra_index, + .len = captures_len, }, }; }, + .type_enum_explicit => .explicit, + .type_enum_nonexhaustive => .nonexhaustive, else => unreachable, + }; + const extra = ip.extraDataTrail(EnumExplicit, item.data); + var extra_index: u32 = @intCast(extra.end); + if (extra.data.zir_index == .none) { + extra_index += 1; // owner_union } + const captures_len = if (extra.data.captures_len == std.math.maxInt(u32)) c: { + extra_index += 2; // type_hash: PackedU64 + break :c 0; + } else extra.data.captures_len; + return .{ + .decl = extra.data.decl, + .namespace = extra.data.namespace, + .tag_ty = extra.data.int_tag_type, + .names = .{ + .start = extra_index + captures_len, + .len = extra.data.fields_len, + }, + .values = .{ + .start = extra_index + captures_len + extra.data.fields_len, + .len = if (extra.data.values_map != .none) extra.data.fields_len else 0, + }, + .tag_mode = tag_mode, + .names_map = extra.data.names_map, + .values_map = extra.data.values_map, + .zir_index = extra.data.zir_index, + .captures = .{ + .start = extra_index, + .len = captures_len, + }, + }; } /// Note that this type doubles as the payload for `Tag.type_opaque`. @@ -2484,9 +2518,9 @@ pub const LoadedOpaqueType = struct { /// The opaque's owner Decl. decl: DeclIndex, /// Contains the declarations inside this opaque. - namespace: NamespaceIndex, - /// The index of the `opaque_decl` instruction. - zir_index: TrackedInst.Index.Optional, + namespace: OptionalNamespaceIndex, + /// Index of the `opaque_decl` or `reify` instruction. + zir_index: TrackedInst.Index, captures: CaptureValue.Slice, }; @@ -2494,13 +2528,17 @@ pub fn loadOpaqueType(ip: *const InternPool, index: Index) LoadedOpaqueType { assert(ip.items.items(.tag)[@intFromEnum(index)] == .type_opaque); const extra_index = ip.items.items(.data)[@intFromEnum(index)]; const extra = ip.extraDataTrail(Tag.TypeOpaque, extra_index); + const captures_len = if (extra.data.captures_len == std.math.maxInt(u32)) + 0 + else + extra.data.captures_len; return .{ .decl = extra.data.decl, .namespace = extra.data.namespace, .zir_index = extra.data.zir_index, .captures = .{ .start = extra.end, - .len = extra.data.captures_len, + .len = captures_len, }, }; } @@ -2693,6 +2731,7 @@ pub const Index = enum(u32) { }, }; + removed: void, type_int_signed: struct { data: u32 }, type_int_unsigned: struct { data: u32 }, type_array_big: struct { data: *Array }, @@ -3100,6 +3139,12 @@ comptime { } pub const Tag = enum(u8) { + /// This special tag represents a value which was removed from this pool via + /// `InternPool.remove`. The item remains allocated to preserve indices, but + /// lookups will consider it not equal to any other item, and all queries + /// assert not this tag. `data` is unused. + removed, + /// An integer type. /// data is number of bits type_int_signed, @@ -3367,6 +3412,7 @@ pub const Tag = enum(u8) { fn Payload(comptime tag: Tag) type { return switch (tag) { + .removed => unreachable, .type_int_signed => unreachable, .type_int_unsigned => unreachable, .type_array_big => Array, @@ -3544,8 +3590,9 @@ pub const Tag = enum(u8) { /// Trailing: /// 0. captures_len: u32 // if `any_captures` /// 1. capture: CaptureValue // for each `captures_len` - /// 2. field type: Index for each field; declaration order - /// 3. field align: Alignment for each field; declaration order + /// 2. type_hash: PackedU64 // if `is_reified` + /// 3. field type: Index for each field; declaration order + /// 4. field align: Alignment for each field; declaration order pub const TypeUnion = struct { flags: Flags, /// This could be provided through the tag type, but it is more convenient @@ -3557,10 +3604,10 @@ pub const Tag = enum(u8) { /// Only valid after .have_layout padding: u32, decl: DeclIndex, - namespace: NamespaceIndex, + namespace: OptionalNamespaceIndex, /// The enum that provides the list of field names and values. tag_ty: Index, - zir_index: TrackedInst.Index.Optional, + zir_index: TrackedInst.Index, pub const Flags = packed struct(u32) { any_captures: bool, @@ -3573,19 +3620,21 @@ pub const Tag = enum(u8) { assumed_runtime_bits: bool, assumed_pointer_aligned: bool, alignment: Alignment, - _: u13 = 0, + is_reified: bool, + _: u12 = 0, }; }; /// Trailing: /// 0. captures_len: u32 // if `any_captures` /// 1. capture: CaptureValue // for each `captures_len` - /// 2. type: Index for each fields_len - /// 3. name: NullTerminatedString for each fields_len - /// 4. init: Index for each fields_len // if tag is type_struct_packed_inits + /// 2. type_hash: PackedU64 // if `is_reified` + /// 3. type: Index for each fields_len + /// 4. name: NullTerminatedString for each fields_len + /// 5. init: Index for each fields_len // if tag is type_struct_packed_inits pub const TypeStructPacked = struct { decl: DeclIndex, - zir_index: TrackedInst.Index.Optional, + zir_index: TrackedInst.Index, fields_len: u32, namespace: OptionalNamespaceIndex, backing_int_ty: Index, @@ -3597,7 +3646,8 @@ pub const Tag = enum(u8) { /// Dependency loop detection when resolving field inits. field_inits_wip: bool, inits_resolved: bool, - _: u29 = 0, + is_reified: bool, + _: u28 = 0, }; }; @@ -3618,24 +3668,25 @@ pub const Tag = enum(u8) { /// Trailing: /// 0. captures_len: u32 // if `any_captures` /// 1. capture: CaptureValue // for each `captures_len` - /// 2. type: Index for each field in declared order - /// 3. if not is_tuple: + /// 2. type_hash: PackedU64 // if `is_reified` + /// 3. type: Index for each field in declared order + /// 4. if not is_tuple: /// names_map: MapIndex, /// name: NullTerminatedString // for each field in declared order - /// 4. if any_default_inits: + /// 5. if any_default_inits: /// init: Index // for each field in declared order - /// 5. if has_namespace: + /// 6. if has_namespace: /// namespace: NamespaceIndex - /// 6. if any_aligned_fields: + /// 7. if any_aligned_fields: /// align: Alignment // for each field in declared order - /// 7. if any_comptime_fields: + /// 8. if any_comptime_fields: /// field_is_comptime_bits: u32 // minimal number of u32s needed, LSB is field 0 - /// 8. if not is_extern: + /// 9. if not is_extern: /// field_index: RuntimeOrder // for each field in runtime order - /// 9. field_offset: u32 // for each field in declared order, undef until layout_resolved + /// 10. field_offset: u32 // for each field in declared order, undef until layout_resolved pub const TypeStruct = struct { decl: DeclIndex, - zir_index: TrackedInst.Index.Optional, + zir_index: TrackedInst.Index, fields_len: u32, flags: Flags, size: u32, @@ -3670,8 +3721,8 @@ pub const Tag = enum(u8) { // The types and all its fields have had their layout resolved. Even through pointer, // which `layout_resolved` does not ensure. fully_resolved: bool, - - _: u7 = 0, + is_reified: bool, + _: u6 = 0, }; }; @@ -3681,9 +3732,10 @@ pub const Tag = enum(u8) { /// The opaque's owner Decl. decl: DeclIndex, /// Contains the declarations inside this opaque. - namespace: NamespaceIndex, + namespace: OptionalNamespaceIndex, /// The index of the `opaque_decl` instruction. - zir_index: TrackedInst.Index.Optional, + zir_index: TrackedInst.Index, + /// `std.math.maxInt(u32)` indicates this type is reified. captures_len: u32, }; }; @@ -3992,12 +4044,15 @@ pub const Array = struct { }; /// Trailing: -/// 0. capture: CaptureValue // for each `captures_len` -/// 1. field name: NullTerminatedString for each fields_len; declaration order -/// 2. tag value: Index for each fields_len; declaration order +/// 0. owner_union: Index // if `zir_index == .none` +/// 1. capture: CaptureValue // for each `captures_len` +/// 2. type_hash: PackedU64 // if reified (`captures_len == std.math.maxInt(u32)`) +/// 3. field name: NullTerminatedString for each fields_len; declaration order +/// 4. tag value: Index for each fields_len; declaration order pub const EnumExplicit = struct { /// The Decl that corresponds to the enum itself. decl: DeclIndex, + /// `std.math.maxInt(u32)` indicates this type is reified. captures_len: u32, /// This may be `none` if there are no declarations. namespace: OptionalNamespaceIndex, @@ -4011,15 +4066,20 @@ pub const EnumExplicit = struct { /// If this is `none`, it means the trailing tag values are absent because /// they are auto-numbered. values_map: OptionalMapIndex, + /// `none` means this is a generated tag type. + /// There will be a trailing union type for which this is a tag. zir_index: TrackedInst.Index.Optional, }; /// Trailing: -/// 0. capture: CaptureValue // for each `captures_len` -/// 1. field name: NullTerminatedString for each fields_len; declaration order +/// 0. owner_union: Index // if `zir_index == .none` +/// 1. capture: CaptureValue // for each `captures_len` +/// 2. type_hash: PackedU64 // if reified (`captures_len == std.math.maxInt(u32)`) +/// 3. field name: NullTerminatedString for each fields_len; declaration order pub const EnumAuto = struct { /// The Decl that corresponds to the enum itself. decl: DeclIndex, + /// `std.math.maxInt(u32)` indicates this type is reified. captures_len: u32, /// This may be `none` if there are no declarations. namespace: OptionalNamespaceIndex, @@ -4029,6 +4089,8 @@ pub const EnumAuto = struct { fields_len: u32, /// Maps field names to declaration index. names_map: MapIndex, + /// `none` means this is a generated tag type. + /// There will be a trailing union type for which this is a tag. zir_index: TrackedInst.Index.Optional, }; @@ -4269,6 +4331,7 @@ pub fn indexToKey(ip: *const InternPool, index: Index) Key { const item = ip.items.get(@intFromEnum(index)); const data = item.data; return switch (item.tag) { + .removed => unreachable, .type_int_signed => .{ .int_type = .{ .signedness = .signed, @@ -4330,31 +4393,123 @@ pub fn indexToKey(ip: *const InternPool, index: Index) Key { .inferred_error_set_type = @enumFromInt(data), }, - .type_opaque => .{ .opaque_type = .{ - .decl = ip.extraData(Tag.TypeOpaque, data).decl, + .type_opaque => .{ .opaque_type = ns: { + const extra = ip.extraDataTrail(Tag.TypeOpaque, data); + if (extra.data.captures_len == std.math.maxInt(u32)) { + break :ns .{ .reified = .{ + .zir_index = extra.data.zir_index, + .type_hash = 0, + } }; + } + break :ns .{ .declared = .{ + .zir_index = extra.data.zir_index, + .captures = .{ .owned = .{ + .start = extra.end, + .len = extra.data.captures_len, + } }, + } }; } }, - .type_struct => .{ .struct_type = if (data == 0) .{ - .decl = .none, - } else .{ - .decl = ip.extraData(Tag.TypeStruct, data).decl.toOptional(), + .type_struct => .{ .struct_type = ns: { + if (data == 0) break :ns .empty_struct; + const extra = ip.extraDataTrail(Tag.TypeStruct, data); + if (extra.data.flags.is_reified) { + assert(!extra.data.flags.any_captures); + break :ns .{ .reified = .{ + .zir_index = extra.data.zir_index, + .type_hash = ip.extraData(PackedU64, extra.end).get(), + } }; + } + break :ns .{ .declared = .{ + .zir_index = extra.data.zir_index, + .captures = .{ .owned = if (extra.data.flags.any_captures) .{ + .start = extra.end + 1, + .len = ip.extra.items[extra.end], + } else .{ .start = 0, .len = 0 } }, + } }; } }, - .type_struct_packed, .type_struct_packed_inits => .{ .struct_type = .{ - .decl = ip.extraData(Tag.TypeStructPacked, data).decl.toOptional(), + .type_struct_packed, .type_struct_packed_inits => .{ .struct_type = ns: { + const extra = ip.extraDataTrail(Tag.TypeStructPacked, data); + if (extra.data.flags.is_reified) { + assert(!extra.data.flags.any_captures); + break :ns .{ .reified = .{ + .zir_index = extra.data.zir_index, + .type_hash = ip.extraData(PackedU64, extra.end).get(), + } }; + } + break :ns .{ .declared = .{ + .zir_index = extra.data.zir_index, + .captures = .{ .owned = if (extra.data.flags.any_captures) .{ + .start = extra.end + 1, + .len = ip.extra.items[extra.end], + } else .{ .start = 0, .len = 0 } }, + } }; } }, .type_struct_anon => .{ .anon_struct_type = extraTypeStructAnon(ip, data) }, .type_tuple_anon => .{ .anon_struct_type = extraTypeTupleAnon(ip, data) }, - .type_union => .{ .union_type = .{ - .decl = ip.extraData(Tag.TypeUnion, data).decl, + .type_union => .{ .union_type = ns: { + const extra = ip.extraDataTrail(Tag.TypeUnion, data); + if (extra.data.flags.is_reified) { + assert(!extra.data.flags.any_captures); + break :ns .{ .reified = .{ + .zir_index = extra.data.zir_index, + .type_hash = ip.extraData(PackedU64, extra.end).get(), + } }; + } + break :ns .{ .declared = .{ + .zir_index = extra.data.zir_index, + .captures = .{ .owned = if (extra.data.flags.any_captures) .{ + .start = extra.end + 1, + .len = ip.extra.items[extra.end], + } else .{ .start = 0, .len = 0 } }, + } }; } }, - .type_enum_auto => .{ .enum_type = .{ - .decl = ip.extraData(EnumAuto, data).decl, + .type_enum_auto => .{ .enum_type = ns: { + const extra = ip.extraDataTrail(EnumAuto, data); + const zir_index = extra.data.zir_index.unwrap() orelse { + assert(extra.data.captures_len == 0); + break :ns .{ .generated_tag = .{ + .union_type = @enumFromInt(ip.extra.items[extra.end]), + } }; + }; + if (extra.data.captures_len == std.math.maxInt(u32)) { + break :ns .{ .reified = .{ + .zir_index = zir_index, + .type_hash = ip.extraData(PackedU64, extra.end).get(), + } }; + } + break :ns .{ .declared = .{ + .zir_index = zir_index, + .captures = .{ .owned = .{ + .start = extra.end, + .len = extra.data.captures_len, + } }, + } }; } }, - .type_enum_explicit, .type_enum_nonexhaustive => .{ .enum_type = .{ - .decl = ip.extraData(EnumExplicit, data).decl, + .type_enum_explicit, .type_enum_nonexhaustive => .{ .enum_type = ns: { + const extra = ip.extraDataTrail(EnumExplicit, data); + const zir_index = extra.data.zir_index.unwrap() orelse { + assert(extra.data.captures_len == 0); + break :ns .{ .generated_tag = .{ + .union_type = @enumFromInt(ip.extra.items[extra.end]), + } }; + }; + if (extra.data.captures_len == std.math.maxInt(u32)) { + break :ns .{ .reified = .{ + .zir_index = zir_index, + .type_hash = ip.extraData(PackedU64, extra.end).get(), + } }; + } + break :ns .{ .declared = .{ + .zir_index = zir_index, + .captures = .{ .owned = .{ + .start = extra.end, + .len = extra.data.captures_len, + } }, + } }; } }, .type_function => .{ .func_type = ip.extraFuncType(data) }, @@ -4979,7 +5134,7 @@ pub fn get(ip: *InternPool, gpa: Allocator, key: Key) Allocator.Error!Index { .union_type => unreachable, // use getUnionType() instead .opaque_type => unreachable, // use getOpaqueType() instead - .enum_type => unreachable, // use getEnum() or getIncompleteEnum() instead + .enum_type => unreachable, // use getEnumType() instead .func_type => unreachable, // use getFuncType() instead .extern_func => unreachable, // use getExternFunc() instead .func => unreachable, // use getFuncInstance() or getFuncDecl() instead @@ -5652,9 +5807,7 @@ pub const UnionTypeInit = struct { assumed_pointer_aligned: bool, alignment: Alignment, }, - decl: DeclIndex, - namespace: NamespaceIndex, - zir_index: TrackedInst.Index.Optional, + has_namespace: bool, fields_len: u32, enum_tag_ty: Index, /// May have length 0 which leaves the values unset until later. @@ -5663,23 +5816,50 @@ pub const UnionTypeInit = struct { /// The logic for `any_aligned_fields` is asserted to have been done before /// calling this function. field_aligns: []const Alignment, - captures: []const CaptureValue, + key: union(enum) { + declared: struct { + zir_index: TrackedInst.Index, + captures: []const CaptureValue, + }, + reified: struct { + zir_index: TrackedInst.Index, + type_hash: u64, + }, + }, }; -pub fn getUnionType(ip: *InternPool, gpa: Allocator, ini: UnionTypeInit) Allocator.Error!Index { - const prev_extra_len = ip.extra.items.len; +pub fn getUnionType(ip: *InternPool, gpa: Allocator, ini: UnionTypeInit) Allocator.Error!WipNamespaceType.Result { + const adapter: KeyAdapter = .{ .intern_pool = ip }; + const gop = try ip.map.getOrPutAdapted(gpa, Key{ .union_type = switch (ini.key) { + .declared => |d| .{ .declared = .{ + .zir_index = d.zir_index, + .captures = .{ .external = d.captures }, + } }, + .reified => |r| .{ .reified = .{ + .zir_index = r.zir_index, + .type_hash = r.type_hash, + } }, + } }, adapter); + if (gop.found_existing) return .{ .existing = @enumFromInt(gop.index) }; + errdefer _ = ip.map.pop(); + const align_elements_len = if (ini.flags.any_aligned_fields) (ini.fields_len + 3) / 4 else 0; const align_element: u32 = @bitCast([1]u8{@intFromEnum(Alignment.none)} ** 4); try ip.extra.ensureUnusedCapacity(gpa, @typeInfo(Tag.TypeUnion).Struct.fields.len + - @intFromBool(ini.captures.len != 0) + // captures_len - ini.captures.len + // captures + // TODO: fmt bug + // zig fmt: off + switch (ini.key) { + .declared => |d| @intFromBool(d.captures.len != 0) + d.captures.len, + .reified => 2, // type_hash: PackedU64 + } + + // zig fmt: on ini.fields_len + // field types align_elements_len); try ip.items.ensureUnusedCapacity(gpa, 1); - const union_type_extra_index = ip.addExtraAssumeCapacity(Tag.TypeUnion{ + const extra_index = ip.addExtraAssumeCapacity(Tag.TypeUnion{ .flags = .{ - .any_captures = ini.captures.len != 0, + .any_captures = ini.key == .declared and ini.key.declared.captures.len != 0, .runtime_tag = ini.flags.runtime_tag, .any_aligned_fields = ini.flags.any_aligned_fields, .layout = ini.flags.layout, @@ -5688,19 +5868,30 @@ pub fn getUnionType(ip: *InternPool, gpa: Allocator, ini: UnionTypeInit) Allocat .assumed_runtime_bits = ini.flags.assumed_runtime_bits, .assumed_pointer_aligned = ini.flags.assumed_pointer_aligned, .alignment = ini.flags.alignment, + .is_reified = ini.key == .reified, }, .fields_len = ini.fields_len, .size = std.math.maxInt(u32), .padding = std.math.maxInt(u32), - .decl = ini.decl, - .namespace = ini.namespace, + .decl = undefined, // set by `finish` + .namespace = .none, // set by `finish` .tag_ty = ini.enum_tag_ty, - .zir_index = ini.zir_index, + .zir_index = switch (ini.key) { + inline else => |x| x.zir_index, + }, }); - if (ini.captures.len != 0) { - ip.extra.appendAssumeCapacity(@intCast(ini.captures.len)); - ip.extra.appendSliceAssumeCapacity(@ptrCast(ini.captures)); + ip.items.appendAssumeCapacity(.{ + .tag = .type_union, + .data = extra_index, + }); + + switch (ini.key) { + .declared => |d| if (d.captures.len != 0) { + ip.extra.appendAssumeCapacity(@intCast(d.captures.len)); + ip.extra.appendSliceAssumeCapacity(@ptrCast(d.captures)); + }, + .reified => |r| _ = ip.addExtraAssumeCapacity(PackedU64.init(r.type_hash)), } // field types @@ -5725,27 +5916,41 @@ pub fn getUnionType(ip: *InternPool, gpa: Allocator, ini: UnionTypeInit) Allocat assert(ini.field_aligns.len == 0); } - const adapter: KeyAdapter = .{ .intern_pool = ip }; - const gop = try ip.map.getOrPutAdapted(gpa, Key{ - .union_type = .{ .decl = ini.decl }, - }, adapter); - if (gop.found_existing) { - ip.extra.items.len = prev_extra_len; - return @enumFromInt(gop.index); - } - - ip.items.appendAssumeCapacity(.{ - .tag = .type_union, - .data = union_type_extra_index, - }); - return @enumFromInt(ip.items.len - 1); + return .{ .wip = .{ + .index = @enumFromInt(ip.items.len - 1), + .decl_extra_index = extra_index + std.meta.fieldIndex(Tag.TypeUnion, "decl").?, + .namespace_extra_index = if (ini.has_namespace) + extra_index + std.meta.fieldIndex(Tag.TypeUnion, "namespace").? + else + null, + } }; } +pub const WipNamespaceType = struct { + index: Index, + decl_extra_index: u32, + namespace_extra_index: ?u32, + pub fn finish(wip: WipNamespaceType, ip: *InternPool, decl: DeclIndex, namespace: OptionalNamespaceIndex) Index { + ip.extra.items[wip.decl_extra_index] = @intFromEnum(decl); + if (wip.namespace_extra_index) |i| { + ip.extra.items[i] = @intFromEnum(namespace.unwrap().?); + } else { + assert(namespace == .none); + } + return wip.index; + } + pub fn cancel(wip: WipNamespaceType, ip: *InternPool) void { + ip.remove(wip.index); + } + + pub const Result = union(enum) { + wip: WipNamespaceType, + existing: Index, + }; +}; + pub const StructTypeInit = struct { - decl: DeclIndex, - namespace: OptionalNamespaceIndex, layout: std.builtin.Type.ContainerLayout, - zir_index: TrackedInst.Index.Optional, fields_len: u32, known_non_opv: bool, requires_comptime: RequiresComptime, @@ -5754,61 +5959,101 @@ pub const StructTypeInit = struct { any_default_inits: bool, inits_resolved: bool, any_aligned_fields: bool, - captures: []const CaptureValue, + has_namespace: bool, + key: union(enum) { + declared: struct { + zir_index: TrackedInst.Index, + captures: []const CaptureValue, + }, + reified: struct { + zir_index: TrackedInst.Index, + type_hash: u64, + }, + }, }; pub fn getStructType( ip: *InternPool, gpa: Allocator, ini: StructTypeInit, -) Allocator.Error!Index { +) Allocator.Error!WipNamespaceType.Result { const adapter: KeyAdapter = .{ .intern_pool = ip }; - const key: Key = .{ - .struct_type = .{ .decl = ini.decl.toOptional() }, - }; + const key: Key = .{ .struct_type = switch (ini.key) { + .declared => |d| .{ .declared = .{ + .zir_index = d.zir_index, + .captures = .{ .external = d.captures }, + } }, + .reified => |r| .{ .reified = .{ + .zir_index = r.zir_index, + .type_hash = r.type_hash, + } }, + } }; const gop = try ip.map.getOrPutAdapted(gpa, key, adapter); - if (gop.found_existing) return @enumFromInt(gop.index); + if (gop.found_existing) return .{ .existing = @enumFromInt(gop.index) }; errdefer _ = ip.map.pop(); const names_map = try ip.addMap(gpa, ini.fields_len); errdefer _ = ip.maps.pop(); + const zir_index = switch (ini.key) { + inline else => |x| x.zir_index, + }; + const is_extern = switch (ini.layout) { .Auto => false, .Extern => true, .Packed => { try ip.extra.ensureUnusedCapacity(gpa, @typeInfo(Tag.TypeStructPacked).Struct.fields.len + - @intFromBool(ini.captures.len != 0) + // captures_len - ini.captures.len + // captures + // TODO: fmt bug + // zig fmt: off + switch (ini.key) { + .declared => |d| @intFromBool(d.captures.len != 0) + d.captures.len, + .reified => 2, // type_hash: PackedU64 + } + + // zig fmt: on ini.fields_len + // types ini.fields_len + // names ini.fields_len); // inits + const extra_index = ip.addExtraAssumeCapacity(Tag.TypeStructPacked{ + .decl = undefined, // set by `finish` + .zir_index = zir_index, + .fields_len = ini.fields_len, + .namespace = .none, + .backing_int_ty = .none, + .names_map = names_map, + .flags = .{ + .any_captures = ini.key == .declared and ini.key.declared.captures.len != 0, + .field_inits_wip = false, + .inits_resolved = ini.inits_resolved, + .is_reified = ini.key == .reified, + }, + }); try ip.items.append(gpa, .{ .tag = if (ini.any_default_inits) .type_struct_packed_inits else .type_struct_packed, - .data = ip.addExtraAssumeCapacity(Tag.TypeStructPacked{ - .decl = ini.decl, - .zir_index = ini.zir_index, - .fields_len = ini.fields_len, - .namespace = ini.namespace, - .backing_int_ty = .none, - .names_map = names_map, - .flags = .{ - .any_captures = ini.captures.len != 0, - .field_inits_wip = false, - .inits_resolved = ini.inits_resolved, - }, - }), + .data = extra_index, }); - if (ini.captures.len != 0) { - ip.extra.appendAssumeCapacity(@intCast(ini.captures.len)); - ip.extra.appendSliceAssumeCapacity(@ptrCast(ini.captures)); + switch (ini.key) { + .declared => |d| if (d.captures.len != 0) { + ip.extra.appendAssumeCapacity(@intCast(d.captures.len)); + ip.extra.appendSliceAssumeCapacity(@ptrCast(d.captures)); + }, + .reified => |r| { + _ = ip.addExtraAssumeCapacity(PackedU64.init(r.type_hash)); + }, } ip.extra.appendNTimesAssumeCapacity(@intFromEnum(Index.none), ini.fields_len); ip.extra.appendNTimesAssumeCapacity(@intFromEnum(OptionalNullTerminatedString.none), ini.fields_len); if (ini.any_default_inits) { ip.extra.appendNTimesAssumeCapacity(@intFromEnum(Index.none), ini.fields_len); } - return @enumFromInt(ip.items.len - 1); + return .{ .wip = .{ + .index = @enumFromInt(ip.items.len - 1), + .decl_extra_index = extra_index + std.meta.fieldIndex(Tag.TypeStructPacked, "decl").?, + .namespace_extra_index = if (ini.has_namespace) + extra_index + std.meta.fieldIndex(Tag.TypeStructPacked, "namespace").? + else + null, + } }; }, }; @@ -5817,44 +6062,56 @@ pub fn getStructType( const comptime_elements_len = if (ini.any_comptime_fields) (ini.fields_len + 31) / 32 else 0; try ip.extra.ensureUnusedCapacity(gpa, @typeInfo(Tag.TypeStruct).Struct.fields.len + - @intFromBool(ini.captures.len != 0) + // captures_len - ini.captures.len + // captures + // TODO: fmt bug + // zig fmt: off + switch (ini.key) { + .declared => |d| @intFromBool(d.captures.len != 0) + d.captures.len, + .reified => 2, // type_hash: PackedU64 + } + + // zig fmt: on (ini.fields_len * 5) + // types, names, inits, runtime order, offsets align_elements_len + comptime_elements_len + 2); // names_map + namespace + const extra_index = ip.addExtraAssumeCapacity(Tag.TypeStruct{ + .decl = undefined, // set by `finish` + .zir_index = zir_index, + .fields_len = ini.fields_len, + .size = std.math.maxInt(u32), + .flags = .{ + .any_captures = ini.key == .declared and ini.key.declared.captures.len != 0, + .is_extern = is_extern, + .known_non_opv = ini.known_non_opv, + .requires_comptime = ini.requires_comptime, + .is_tuple = ini.is_tuple, + .assumed_runtime_bits = false, + .assumed_pointer_aligned = false, + .has_namespace = ini.has_namespace, + .any_comptime_fields = ini.any_comptime_fields, + .any_default_inits = ini.any_default_inits, + .any_aligned_fields = ini.any_aligned_fields, + .alignment = .none, + .alignment_wip = false, + .field_types_wip = false, + .layout_wip = false, + .layout_resolved = false, + .field_inits_wip = false, + .inits_resolved = ini.inits_resolved, + .fully_resolved = false, + .is_reified = ini.key == .reified, + }, + }); try ip.items.append(gpa, .{ .tag = .type_struct, - .data = ip.addExtraAssumeCapacity(Tag.TypeStruct{ - .decl = ini.decl, - .zir_index = ini.zir_index, - .fields_len = ini.fields_len, - .size = std.math.maxInt(u32), - .flags = .{ - .any_captures = ini.captures.len != 0, - .is_extern = is_extern, - .known_non_opv = ini.known_non_opv, - .requires_comptime = ini.requires_comptime, - .is_tuple = ini.is_tuple, - .assumed_runtime_bits = false, - .assumed_pointer_aligned = false, - .has_namespace = ini.namespace != .none, - .any_comptime_fields = ini.any_comptime_fields, - .any_default_inits = ini.any_default_inits, - .any_aligned_fields = ini.any_aligned_fields, - .alignment = .none, - .alignment_wip = false, - .field_types_wip = false, - .layout_wip = false, - .layout_resolved = false, - .field_inits_wip = false, - .inits_resolved = ini.inits_resolved, - .fully_resolved = false, - }, - }), + .data = extra_index, }); - if (ini.captures.len != 0) { - ip.extra.appendAssumeCapacity(@intCast(ini.captures.len)); - ip.extra.appendSliceAssumeCapacity(@ptrCast(ini.captures)); + switch (ini.key) { + .declared => |d| if (d.captures.len != 0) { + ip.extra.appendAssumeCapacity(@intCast(d.captures.len)); + ip.extra.appendSliceAssumeCapacity(@ptrCast(d.captures)); + }, + .reified => |r| { + _ = ip.addExtraAssumeCapacity(PackedU64.init(r.type_hash)); + }, } ip.extra.appendNTimesAssumeCapacity(@intFromEnum(Index.none), ini.fields_len); if (!ini.is_tuple) { @@ -5864,9 +6121,10 @@ pub fn getStructType( if (ini.any_default_inits) { ip.extra.appendNTimesAssumeCapacity(@intFromEnum(Index.none), ini.fields_len); } - if (ini.namespace.unwrap()) |namespace| { - ip.extra.appendAssumeCapacity(@intFromEnum(namespace)); - } + const namespace_extra_index: ?u32 = if (ini.has_namespace) i: { + ip.extra.appendAssumeCapacity(undefined); // set by `finish` + break :i @intCast(ip.extra.items.len - 1); + } else null; if (ini.any_aligned_fields) { ip.extra.appendNTimesAssumeCapacity(align_element, align_elements_len); } @@ -5877,7 +6135,11 @@ pub fn getStructType( ip.extra.appendNTimesAssumeCapacity(@intFromEnum(LoadedStructType.RuntimeOrder.unresolved), ini.fields_len); } ip.extra.appendNTimesAssumeCapacity(std.math.maxInt(u32), ini.fields_len); - return @enumFromInt(ip.items.len - 1); + return .{ .wip = .{ + .index = @enumFromInt(ip.items.len - 1), + .decl_extra_index = extra_index + std.meta.fieldIndex(Tag.TypeStruct, "decl").?, + .namespace_extra_index = namespace_extra_index, + } }; } pub const AnonStructTypeInit = struct { @@ -6513,282 +6775,399 @@ fn finishFuncInstance( return func_index; } -/// Provides API for completing an enum type after calling `getIncompleteEnum`. -pub const IncompleteEnumType = struct { +pub const EnumTypeInit = struct { + has_namespace: bool, + has_values: bool, + tag_mode: LoadedEnumType.TagMode, + fields_len: u32, + key: union(enum) { + declared: struct { + zir_index: TrackedInst.Index, + captures: []const CaptureValue, + }, + reified: struct { + zir_index: TrackedInst.Index, + type_hash: u64, + }, + }, +}; + +pub const WipEnumType = struct { index: Index, tag_ty_index: u32, + decl_index: u32, + namespace_index: ?u32, names_map: MapIndex, names_start: u32, values_map: OptionalMapIndex, values_start: u32, + expected_fields_len: if (std.debug.runtime_safety) u32 else void, - pub fn setTagType(self: @This(), ip: *InternPool, tag_ty: Index) void { - assert(tag_ty == .noreturn_type or ip.isIntegerType(tag_ty)); - ip.extra.items[self.tag_ty_index] = @intFromEnum(tag_ty); + pub fn prepare( + wip: WipEnumType, + ip: *InternPool, + decl: DeclIndex, + namespace: OptionalNamespaceIndex, + tag_ty: Index, + ) void { + assert(ip.isIntegerType(tag_ty)); + ip.extra.items[wip.tag_ty_index] = @intFromEnum(tag_ty); + ip.extra.items[wip.decl_index] = @intFromEnum(decl); + if (wip.namespace_index) |i| { + ip.extra.items[i] = @intFromEnum(namespace.unwrap().?); + } else { + assert(namespace == .none); + } } - /// 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.names_start, name); - } + pub const FieldConflict = struct { + kind: enum { name, value }, + prev_field_idx: u32, + }; - /// Returns the already-existing field with the same value, if any. - /// Make sure the type of the value has the integer tag type of the enum. - pub fn addFieldValue( - self: @This(), - ip: *InternPool, - value: Index, - ) ?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().?)]; + /// Returns the already-existing field with the same name or value, if any. + /// If the enum is automatially numbered, `value` must be `.none`. + /// Otherwise, the type of `value` must be the integer tag type of the enum. + pub fn nextField(wip: WipEnumType, ip: *InternPool, name: NullTerminatedString, value: Index) ?FieldConflict { + if (ip.addFieldName(wip.names_map, wip.names_start, name)) |conflict| { + return .{ .kind = .name, .prev_field_idx = conflict }; + } + if (value == .none) { + assert(wip.values_map == .none); + return null; + } + assert(ip.typeOf(value) == @as(Index, @enumFromInt(ip.extra.items[wip.tag_ty_index]))); + const map = &ip.maps.items[@intFromEnum(wip.values_map.unwrap().?)]; const field_index = map.count(); - const indexes = ip.extra.items[self.values_start..][0..field_index]; + const indexes = ip.extra.items[wip.values_start..][0..field_index]; 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); + if (gop.found_existing) { + return .{ .kind = .value, .prev_field_idx = @intCast(gop.index) }; + } + ip.extra.items[wip.values_start + field_index] = @intFromEnum(value); return null; } + + pub fn finish(wip: WipEnumType, ip: *InternPool) Index { + if (std.debug.runtime_safety) { + const names_map = &ip.maps.items[@intFromEnum(wip.names_map)]; + assert(names_map.count() == wip.expected_fields_len); + if (wip.values_map.unwrap()) |v| { + const values_map = &ip.maps.items[@intFromEnum(v)]; + assert(values_map.count() == wip.expected_fields_len); + } + } + return wip.index; + } + + pub fn cancel(wip: WipEnumType, ip: *InternPool) void { + ip.remove(wip.index); + } + + pub const Result = union(enum) { + wip: WipEnumType, + existing: Index, + }; }; -/// This is used to create an enum type in the `InternPool`, with the ability -/// to update the tag type, field names, and field values later. -pub fn getIncompleteEnum( +pub fn getEnumType( ip: *InternPool, gpa: Allocator, - enum_type: Key.IncompleteEnumType, -) Allocator.Error!IncompleteEnumType { - switch (enum_type.tag_mode) { - .auto => return getIncompleteEnumAuto(ip, gpa, enum_type), - .explicit => return getIncompleteEnumExplicit(ip, gpa, enum_type, .type_enum_explicit), - .nonexhaustive => return getIncompleteEnumExplicit(ip, gpa, enum_type, .type_enum_nonexhaustive), + ini: EnumTypeInit, +) Allocator.Error!WipEnumType.Result { + const adapter: KeyAdapter = .{ .intern_pool = ip }; + const gop = try ip.map.getOrPutAdapted(gpa, Key{ .enum_type = switch (ini.key) { + .declared => |d| .{ .declared = .{ + .zir_index = d.zir_index, + .captures = .{ .external = d.captures }, + } }, + .reified => |r| .{ .reified = .{ + .zir_index = r.zir_index, + .type_hash = r.type_hash, + } }, + } }, adapter); + if (gop.found_existing) return .{ .existing = @enumFromInt(gop.index) }; + assert(gop.index == ip.items.len); + errdefer _ = ip.map.pop(); + + try ip.items.ensureUnusedCapacity(gpa, 1); + + const names_map = try ip.addMap(gpa, ini.fields_len); + errdefer _ = ip.maps.pop(); + + switch (ini.tag_mode) { + .auto => { + assert(!ini.has_values); + try ip.extra.ensureUnusedCapacity(gpa, @typeInfo(EnumAuto).Struct.fields.len + + // TODO: fmt bug + // zig fmt: off + switch (ini.key) { + .declared => |d| d.captures.len, + .reified => 2, // type_hash: PackedU64 + } + + // zig fmt: on + ini.fields_len); // field types + + const extra_index = ip.addExtraAssumeCapacity(EnumAuto{ + .decl = undefined, // set by `prepare` + .captures_len = switch (ini.key) { + .declared => |d| @intCast(d.captures.len), + .reified => std.math.maxInt(u32), + }, + .namespace = .none, + .int_tag_type = .none, // set by `prepare` + .fields_len = ini.fields_len, + .names_map = names_map, + .zir_index = switch (ini.key) { + inline else => |x| x.zir_index, + }.toOptional(), + }); + ip.items.appendAssumeCapacity(.{ + .tag = .type_enum_auto, + .data = extra_index, + }); + switch (ini.key) { + .declared => |d| ip.extra.appendSliceAssumeCapacity(@ptrCast(d.captures)), + .reified => |r| _ = ip.addExtraAssumeCapacity(PackedU64.init(r.type_hash)), + } + const names_start = ip.extra.items.len; + ip.extra.appendNTimesAssumeCapacity(undefined, ini.fields_len); + return .{ .wip = .{ + .index = @enumFromInt(gop.index), + .tag_ty_index = extra_index + std.meta.fieldIndex(EnumAuto, "int_tag_type").?, + .decl_index = extra_index + std.meta.fieldIndex(EnumAuto, "decl").?, + .namespace_index = if (ini.has_namespace) extra_index + std.meta.fieldIndex(EnumAuto, "namespace").? else null, + .names_map = names_map, + .names_start = @intCast(names_start), + .values_map = .none, + .values_start = undefined, + .expected_fields_len = if (std.debug.runtime_safety) ini.fields_len else {}, + } }; + }, + .explicit, .nonexhaustive => { + const values_map: OptionalMapIndex = if (!ini.has_values) .none else m: { + const values_map = try ip.addMap(gpa, ini.fields_len); + break :m values_map.toOptional(); + }; + errdefer if (ini.has_values) { + _ = ip.map.pop(); + }; + + try ip.extra.ensureUnusedCapacity(gpa, @typeInfo(EnumExplicit).Struct.fields.len + + // TODO: fmt bug + // zig fmt: off + switch (ini.key) { + .declared => |d| d.captures.len, + .reified => 2, // type_hash: PackedU64 + } + + // zig fmt: on + ini.fields_len + // field types + ini.fields_len * @intFromBool(ini.has_values)); // field values + + const extra_index = ip.addExtraAssumeCapacity(EnumExplicit{ + .decl = undefined, // set by `prepare` + .captures_len = switch (ini.key) { + .declared => |d| @intCast(d.captures.len), + .reified => std.math.maxInt(u32), + }, + .namespace = .none, + .int_tag_type = .none, // set by `prepare` + .fields_len = ini.fields_len, + .names_map = names_map, + .values_map = values_map, + .zir_index = switch (ini.key) { + inline else => |x| x.zir_index, + }.toOptional(), + }); + ip.items.appendAssumeCapacity(.{ + .tag = switch (ini.tag_mode) { + .auto => unreachable, + .explicit => .type_enum_explicit, + .nonexhaustive => .type_enum_nonexhaustive, + }, + .data = extra_index, + }); + switch (ini.key) { + .declared => |d| ip.extra.appendSliceAssumeCapacity(@ptrCast(d.captures)), + .reified => |r| _ = ip.addExtraAssumeCapacity(PackedU64.init(r.type_hash)), + } + const names_start = ip.extra.items.len; + ip.extra.appendNTimesAssumeCapacity(undefined, ini.fields_len); + const values_start = ip.extra.items.len; + if (ini.has_values) { + ip.extra.appendNTimesAssumeCapacity(undefined, ini.fields_len); + } + return .{ .wip = .{ + .index = @enumFromInt(gop.index), + .tag_ty_index = extra_index + std.meta.fieldIndex(EnumAuto, "int_tag_type").?, + .decl_index = extra_index + std.meta.fieldIndex(EnumAuto, "decl").?, + .namespace_index = if (ini.has_namespace) extra_index + std.meta.fieldIndex(EnumAuto, "namespace").? else null, + .names_map = names_map, + .names_start = @intCast(names_start), + .values_map = values_map, + .values_start = @intCast(values_start), + .expected_fields_len = if (std.debug.runtime_safety) ini.fields_len else {}, + } }; + }, } } -fn getIncompleteEnumAuto( - ip: *InternPool, - gpa: Allocator, - enum_type: Key.IncompleteEnumType, -) Allocator.Error!IncompleteEnumType { - const int_tag_type = if (enum_type.tag_ty != .none) - enum_type.tag_ty - else - try ip.get(gpa, .{ .int_type = .{ - .bits = if (enum_type.fields_len == 0) 0 else std.math.log2_int_ceil(u32, enum_type.fields_len), - .signedness = .unsigned, - } }); - - // We must keep the map in sync with `items`. The hash and equality functions - // for enum types only look at the decl field, which is present even in - // an `IncompleteEnumType`. - const adapter: KeyAdapter = .{ .intern_pool = ip }; - const gop = try ip.map.getOrPutAdapted(gpa, enum_type.toKey(), adapter); - assert(!gop.found_existing); - - 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.captures.len + enum_type.fields_len); - try ip.items.ensureUnusedCapacity(gpa, 1); - - const extra_index = ip.addExtraAssumeCapacity(EnumAuto{ - .decl = enum_type.decl, - .captures_len = @intCast(enum_type.captures.len), - .namespace = enum_type.namespace, - .int_tag_type = int_tag_type, - .names_map = names_map, - .fields_len = enum_type.fields_len, - .zir_index = enum_type.zir_index, - }); - - ip.items.appendAssumeCapacity(.{ - .tag = .type_enum_auto, - .data = extra_index, - }); - ip.extra.appendSliceAssumeCapacity(@ptrCast(enum_type.captures)); - ip.extra.appendNTimesAssumeCapacity(@intFromEnum(Index.none), enum_type.fields_len); - return .{ - .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, - .values_map = .none, - .values_start = undefined, - }; -} - -fn getIncompleteEnumExplicit( - ip: *InternPool, - gpa: Allocator, - enum_type: Key.IncompleteEnumType, - tag: Tag, -) Allocator.Error!IncompleteEnumType { - // We must keep the map in sync with `items`. The hash and equality functions - // for enum types only look at the decl field, which is present even in - // an `IncompleteEnumType`. - const adapter: KeyAdapter = .{ .intern_pool = ip }; - const gop = try ip.map.getOrPutAdapted(gpa, enum_type.toKey(), adapter); - assert(!gop.found_existing); - - 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, enum_type.fields_len); - break :m values_map.toOptional(); - }; - - const reserved_len = enum_type.fields_len + - if (enum_type.has_values) enum_type.fields_len else 0; - - const extra_fields_len: u32 = @typeInfo(EnumExplicit).Struct.fields.len; - try ip.extra.ensureUnusedCapacity(gpa, extra_fields_len + enum_type.captures.len + reserved_len); - try ip.items.ensureUnusedCapacity(gpa, 1); - - const extra_index = ip.addExtraAssumeCapacity(EnumExplicit{ - .decl = enum_type.decl, - .captures_len = @intCast(enum_type.captures.len), - .namespace = enum_type.namespace, - .int_tag_type = enum_type.tag_ty, - .fields_len = enum_type.fields_len, - .names_map = names_map, - .values_map = values_map, - .zir_index = enum_type.zir_index, - }); - - ip.items.appendAssumeCapacity(.{ - .tag = tag, - .data = extra_index, - }); - ip.extra.appendSliceAssumeCapacity(@ptrCast(enum_type.captures)); - // This is both fields and values (if present). - ip.extra.appendNTimesAssumeCapacity(@intFromEnum(Index.none), reserved_len); - return .{ - .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, - .values_map = values_map, - .values_start = extra_index + extra_fields_len + enum_type.fields_len, - }; -} - -pub const GetEnumInit = struct { +const GeneratedTagEnumTypeInit = struct { decl: DeclIndex, - namespace: OptionalNamespaceIndex, + owner_union_ty: Index, tag_ty: Index, names: []const NullTerminatedString, values: []const Index, tag_mode: LoadedEnumType.TagMode, - zir_index: TrackedInst.Index.Optional, - captures: []const CaptureValue, }; -pub fn getEnum(ip: *InternPool, gpa: Allocator, ini: GetEnumInit) Allocator.Error!Index { - const adapter: KeyAdapter = .{ .intern_pool = ip }; - const gop = try ip.map.getOrPutAdapted(gpa, Key{ - .enum_type = .{ .decl = ini.decl }, - }, adapter); - if (gop.found_existing) return @enumFromInt(gop.index); - errdefer _ = ip.map.pop(); +/// Creates an enum type which was automatically-generated as the tag type of a +/// `union` with no explicit tag type. Since this is only called once per union +/// type, it asserts that no matching type yet exists. +pub fn getGeneratedTagEnumType(ip: *InternPool, gpa: Allocator, ini: GeneratedTagEnumTypeInit) Allocator.Error!Index { + assert(ip.isUnion(ini.owner_union_ty)); + assert(ip.isIntegerType(ini.tag_ty)); + for (ini.values) |val| assert(ip.typeOf(val) == ini.tag_ty); + + try ip.map.ensureUnusedCapacity(gpa, 1); try ip.items.ensureUnusedCapacity(gpa, 1); - assert(ini.tag_ty == .noreturn_type or ip.isIntegerType(ini.tag_ty)); - for (ini.values) |value| assert(ip.typeOf(value) == ini.tag_ty); + const names_map = try ip.addMap(gpa, ini.names.len); + errdefer _ = ip.maps.pop(); + ip.addStringsToMap(names_map, ini.names); + + const fields_len: u32 = @intCast(ini.names.len); switch (ini.tag_mode) { .auto => { - 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 + - ini.captures.len + fields_len); + 1 + // owner_union + fields_len); // field names ip.items.appendAssumeCapacity(.{ .tag = .type_enum_auto, .data = ip.addExtraAssumeCapacity(EnumAuto{ .decl = ini.decl, - .captures_len = @intCast(ini.captures.len), - .namespace = ini.namespace, + .captures_len = 0, + .namespace = .none, .int_tag_type = ini.tag_ty, - .names_map = names_map, .fields_len = fields_len, - .zir_index = ini.zir_index, + .names_map = names_map, + .zir_index = .none, }), }); - ip.extra.appendSliceAssumeCapacity(@ptrCast(ini.captures)); + ip.extra.appendAssumeCapacity(@intFromEnum(ini.owner_union_ty)); ip.extra.appendSliceAssumeCapacity(@ptrCast(ini.names)); - return @enumFromInt(ip.items.len - 1); }, - .explicit => return finishGetEnum(ip, gpa, ini, .type_enum_explicit), - .nonexhaustive => return finishGetEnum(ip, gpa, ini, .type_enum_nonexhaustive), + .explicit, .nonexhaustive => { + try ip.extra.ensureUnusedCapacity(gpa, @typeInfo(EnumExplicit).Struct.fields.len + + 1 + // owner_union + fields_len + // field names + ini.values.len); // field values + + const values_map: OptionalMapIndex = if (ini.values.len != 0) m: { + const map = try ip.addMap(gpa, ini.values.len); + addIndexesToMap(ip, map, ini.values); + break :m map.toOptional(); + } else .none; + // We don't clean up the values map on error! + errdefer @compileError("error path leaks values_map"); + + ip.items.appendAssumeCapacity(.{ + .tag = switch (ini.tag_mode) { + .explicit => .type_enum_explicit, + .nonexhaustive => .type_enum_nonexhaustive, + .auto => unreachable, + }, + .data = ip.addExtraAssumeCapacity(EnumExplicit{ + .decl = ini.decl, + .captures_len = 0, + .namespace = .none, + .int_tag_type = ini.tag_ty, + .fields_len = fields_len, + .names_map = names_map, + .values_map = values_map, + .zir_index = .none, + }), + }); + ip.extra.appendAssumeCapacity(@intFromEnum(ini.owner_union_ty)); + ip.extra.appendSliceAssumeCapacity(@ptrCast(ini.names)); + ip.extra.appendSliceAssumeCapacity(@ptrCast(ini.values)); + }, } -} + // Same as above + errdefer @compileError("error path leaks values_map and extra data"); -fn finishGetEnum( - ip: *InternPool, - gpa: Allocator, - ini: GetEnumInit, - tag: Tag, -) Allocator.Error!Index { - 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, ini.values.len); - addIndexesToMap(ip, values_map, ini.values); - break :m values_map.toOptional(); - }; - const fields_len: u32 = @intCast(ini.names.len); - try ip.extra.ensureUnusedCapacity(gpa, @typeInfo(EnumExplicit).Struct.fields.len + - ini.captures.len + fields_len); - ip.items.appendAssumeCapacity(.{ - .tag = tag, - .data = ip.addExtraAssumeCapacity(EnumExplicit{ - .decl = ini.decl, - .captures_len = @intCast(ini.captures.len), - .namespace = ini.namespace, - .int_tag_type = ini.tag_ty, - .fields_len = fields_len, - .names_map = names_map, - .values_map = values_map, - .zir_index = ini.zir_index, - }), - }); - ip.extra.appendSliceAssumeCapacity(@ptrCast(ini.captures)); - ip.extra.appendSliceAssumeCapacity(@ptrCast(ini.names)); - ip.extra.appendSliceAssumeCapacity(@ptrCast(ini.values)); - return @enumFromInt(ip.items.len - 1); + // Capacity for this was ensured earlier + const adapter: KeyAdapter = .{ .intern_pool = ip }; + const gop = ip.map.getOrPutAssumeCapacityAdapted(Key{ .enum_type = .{ + .generated_tag = .{ .union_type = ini.owner_union_ty }, + } }, adapter); + assert(!gop.found_existing); + assert(gop.index == ip.items.len - 1); + return @enumFromInt(gop.index); } pub const OpaqueTypeIni = struct { - decl: DeclIndex, - namespace: NamespaceIndex, - zir_index: TrackedInst.Index.Optional, - captures: []const CaptureValue, + has_namespace: bool, + key: union(enum) { + declared: struct { + zir_index: TrackedInst.Index, + captures: []const CaptureValue, + }, + reified: struct { + zir_index: TrackedInst.Index, + // No type hash since reifid opaques have no data other than the `@Type` location + }, + }, }; -pub fn getOpaqueType(ip: *InternPool, gpa: Allocator, ini: OpaqueTypeIni) Allocator.Error!Index { +pub fn getOpaqueType(ip: *InternPool, gpa: Allocator, ini: OpaqueTypeIni) Allocator.Error!WipNamespaceType.Result { const adapter: KeyAdapter = .{ .intern_pool = ip }; - try ip.extra.ensureUnusedCapacity(gpa, @typeInfo(LoadedOpaqueType).Struct.fields.len + ini.captures.len); + const gop = try ip.map.getOrPutAdapted(gpa, Key{ .opaque_type = switch (ini.key) { + .declared => |d| .{ .declared = .{ + .zir_index = d.zir_index, + .captures = .{ .external = d.captures }, + } }, + .reified => |r| .{ .reified = .{ + .zir_index = r.zir_index, + .type_hash = 0, + } }, + } }, adapter); + if (gop.found_existing) return .{ .existing = @enumFromInt(gop.index) }; + errdefer _ = ip.map.pop(); try ip.items.ensureUnusedCapacity(gpa, 1); - const gop = try ip.map.getOrPutAdapted(gpa, Key{ - .opaque_type = .{ .decl = ini.decl }, - }, adapter); - if (gop.found_existing) return @enumFromInt(gop.index); + try ip.extra.ensureUnusedCapacity(gpa, @typeInfo(Tag.TypeOpaque).Struct.fields.len + switch (ini.key) { + .declared => |d| d.captures.len, + .reified => 0, + }); + const extra_index = ip.addExtraAssumeCapacity(Tag.TypeOpaque{ + .decl = undefined, // set by `finish` + .namespace = .none, + .zir_index = switch (ini.key) { + inline else => |x| x.zir_index, + }, + .captures_len = switch (ini.key) { + .declared => |d| @intCast(d.captures.len), + .reified => std.math.maxInt(u32), + }, + }); ip.items.appendAssumeCapacity(.{ .tag = .type_opaque, - .data = ip.addExtraAssumeCapacity(Tag.TypeOpaque{ - .decl = ini.decl, - .namespace = ini.namespace, - .zir_index = ini.zir_index, - .captures_len = @intCast(ini.captures.len), - }), + .data = extra_index, }); - ip.extra.appendSliceAssumeCapacity(@ptrCast(ini.captures)); - return @enumFromInt(gop.index); + switch (ini.key) { + .declared => |d| ip.extra.appendSliceAssumeCapacity(@ptrCast(d.captures)), + .reified => {}, + } + return .{ .wip = .{ + .index = @enumFromInt(gop.index), + .decl_extra_index = extra_index + std.meta.fieldIndex(Tag.TypeOpaque, "decl").?, + .namespace_extra_index = if (ini.has_namespace) + extra_index + std.meta.fieldIndex(Tag.TypeOpaque, "namespace").? + else + null, + } }; } pub fn getIfExists(ip: *const InternPool, key: Key) ?Index { @@ -6837,8 +7216,34 @@ fn addMap(ip: *InternPool, gpa: Allocator, cap: usize) Allocator.Error!MapIndex /// This operation only happens under compile error conditions. /// Leak the index until the next garbage collection. -/// TODO: this is a bit problematic to implement, can we get away without it? -pub const remove = @compileError("InternPool.remove is not currently a supported operation; put a TODO there instead"); +/// Invalidates all references to this index. +pub fn remove(ip: *InternPool, index: Index) void { + if (@intFromEnum(index) < static_keys.len) { + // The item being removed replaced a special index via `InternPool.resolveBuiltinType`. + // Restore the original item at this index. + switch (static_keys[@intFromEnum(index)]) { + .simple_type => |s| { + ip.items.set(@intFromEnum(index), .{ + .tag = .simple_type, + .data = @intFromEnum(s), + }); + }, + else => unreachable, + } + return; + } + + if (@intFromEnum(index) == ip.items.len - 1) { + // Happy case - we can just drop the item without affecting any other indices. + ip.items.len -= 1; + _ = ip.map.pop(); + } else { + // We must preserve the item so that indices following it remain valid. + // Thus, we will rewrite the tag to `removed`, leaking the item until + // next GC but causing `KeyAdapter` to ignore it. + ip.items.set(@intFromEnum(index), .{ .tag = .removed, .data = undefined }); + } +} fn addInt(ip: *InternPool, gpa: Allocator, ty: Index, tag: Tag, limbs: []const Limb) !void { const limbs_len = @as(u32, @intCast(limbs.len)); @@ -7635,6 +8040,10 @@ fn dumpStatsFallible(ip: *const InternPool, arena: Allocator) anyerror!void { if (!gop.found_existing) gop.value_ptr.* = .{}; gop.value_ptr.count += 1; gop.value_ptr.bytes += 1 + 4 + @as(usize, switch (tag) { + // Note that in this case, we have technically leaked some extra data + // bytes which we do not account for here. + .removed => 0, + .type_int_signed => 0, .type_int_unsigned => 0, .type_array_small => @sizeOf(Vector), @@ -7852,6 +8261,8 @@ fn dumpAllFallible(ip: *const InternPool) anyerror!void { for (tags, datas, 0..) |tag, data, i| { try w.print("${d} = {s}(", .{ i, @tagName(tag) }); switch (tag) { + .removed => {}, + .simple_type => try w.print("{s}", .{@tagName(@as(SimpleType, @enumFromInt(data)))}), .simple_value => try w.print("{s}", .{@tagName(@as(SimpleValue, @enumFromInt(data)))}), @@ -8258,6 +8669,8 @@ pub fn typeOf(ip: *const InternPool, index: Index) Index { // This optimization on tags is needed so that indexToKey can call // typeOf without being recursive. _ => switch (ip.items.items(.tag)[@intFromEnum(index)]) { + .removed => unreachable, + .type_int_signed, .type_int_unsigned, .type_array_big, @@ -8575,6 +8988,8 @@ pub fn zigTypeTagOrPoison(ip: *const InternPool, index: Index) error{GenericPois .var_args_param_type => unreachable, // special tag _ => switch (ip.items.items(.tag)[@intFromEnum(index)]) { + .removed => unreachable, + .type_int_signed, .type_int_unsigned, => .Int, diff --git a/src/Module.zig b/src/Module.zig index eb3168e08b..4ad760063b 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -568,9 +568,9 @@ pub const Decl = struct { .empty_struct_type => .none, .none => .none, else => switch (ip.indexToKey(decl.val.toIntern())) { - .opaque_type => ip.loadOpaqueType(decl.val.toIntern()).namespace.toOptional(), + .opaque_type => ip.loadOpaqueType(decl.val.toIntern()).namespace, .struct_type => ip.loadStructType(decl.val.toIntern()).namespace, - .union_type => ip.loadUnionType(decl.val.toIntern()).namespace.toOptional(), + .union_type => ip.loadUnionType(decl.val.toIntern()).namespace, .enum_type => ip.loadEnumType(decl.val.toIntern()).namespace, else => .none, }, @@ -3302,6 +3302,70 @@ pub fn semaPkg(mod: *Module, pkg: *Package.Module) !void { return mod.semaFile(file); } +fn getFileRootStruct(zcu: *Zcu, decl_index: Decl.Index, namespace_index: Namespace.Index, file: *File) Allocator.Error!InternPool.Index { + const gpa = zcu.gpa; + const ip = &zcu.intern_pool; + const extended = file.zir.instructions.items(.data)[@intFromEnum(Zir.Inst.Index.main_struct_inst)].extended; + assert(extended.opcode == .struct_decl); + const small: Zir.Inst.StructDecl.Small = @bitCast(extended.small); + assert(!small.has_captures_len); + assert(!small.has_backing_int); + assert(small.layout == .Auto); + var extra_index: usize = extended.operand + @typeInfo(Zir.Inst.StructDecl).Struct.fields.len; + const fields_len = if (small.has_fields_len) blk: { + const fields_len = file.zir.extra[extra_index]; + extra_index += 1; + break :blk fields_len; + } else 0; + const decls_len = if (small.has_decls_len) blk: { + const decls_len = file.zir.extra[extra_index]; + extra_index += 1; + break :blk decls_len; + } else 0; + const decls = file.zir.bodySlice(extra_index, decls_len); + extra_index += decls_len; + + const tracked_inst = try ip.trackZir(gpa, file, .main_struct_inst); + const wip_ty = switch (try ip.getStructType(gpa, .{ + .layout = .Auto, + .fields_len = fields_len, + .known_non_opv = small.known_non_opv, + .requires_comptime = if (small.known_comptime_only) .yes else .unknown, + .is_tuple = small.is_tuple, + .any_comptime_fields = small.any_comptime_fields, + .any_default_inits = small.any_default_inits, + .inits_resolved = false, + .any_aligned_fields = small.any_aligned_fields, + .has_namespace = true, + .key = .{ .declared = .{ + .zir_index = tracked_inst, + .captures = &.{}, + } }, + })) { + .existing => unreachable, // we wouldn't be analysing the file root if this type existed + .wip => |wip| wip, + }; + errdefer wip_ty.cancel(ip); + + if (zcu.comp.debug_incremental) { + try ip.addDependency( + gpa, + InternPool.Depender.wrap(.{ .decl = decl_index }), + .{ .src_hash = tracked_inst }, + ); + } + + const decl = zcu.declPtr(decl_index); + decl.val = Value.fromInterned(wip_ty.index); + decl.has_tv = true; + decl.owns_tv = true; + decl.analysis = .complete; + + try zcu.scanNamespace(namespace_index, decls, decl); + + return wip_ty.finish(ip, decl_index, namespace_index.toOptional()); +} + /// Regardless of the file status, will create a `Decl` so that we /// can track dependencies and re-analyze when the file becomes outdated. pub fn semaFile(mod: *Module, file: *File) SemaError!void { @@ -3323,7 +3387,6 @@ pub fn semaFile(mod: *Module, file: *File) SemaError!void { .decl_index = undefined, .file_scope = file, }); - const new_namespace = mod.namespacePtr(new_namespace_index); errdefer mod.destroyNamespace(new_namespace_index); const new_decl_index = try mod.allocateNewDecl(new_namespace_index, 0); @@ -3331,7 +3394,7 @@ pub fn semaFile(mod: *Module, file: *File) SemaError!void { errdefer @panic("TODO error handling"); file.root_decl = new_decl_index.toOptional(); - new_namespace.decl_index = new_decl_index; + mod.namespacePtr(new_namespace_index).decl_index = new_decl_index; new_decl.name = try file.fullyQualifiedName(mod); new_decl.name_fully_qualified = true; @@ -3350,63 +3413,10 @@ pub fn semaFile(mod: *Module, file: *File) SemaError!void { } assert(file.zir_loaded); - var sema_arena = std.heap.ArenaAllocator.init(gpa); - defer sema_arena.deinit(); - const sema_arena_allocator = sema_arena.allocator(); + const struct_ty = try mod.getFileRootStruct(new_decl_index, new_namespace_index, file); + errdefer mod.intern_pool.remove(struct_ty); - var comptime_mutable_decls = std.ArrayList(Decl.Index).init(gpa); - defer comptime_mutable_decls.deinit(); - - var comptime_err_ret_trace = std.ArrayList(SrcLoc).init(gpa); - defer comptime_err_ret_trace.deinit(); - - var sema: Sema = .{ - .mod = mod, - .gpa = gpa, - .arena = sema_arena_allocator, - .code = file.zir, - .owner_decl = new_decl, - .owner_decl_index = new_decl_index, - .func_index = .none, - .func_is_naked = false, - .fn_ret_ty = Type.void, - .fn_ret_ty_ies = null, - .owner_func_index = .none, - .comptime_mutable_decls = &comptime_mutable_decls, - .comptime_err_ret_trace = &comptime_err_ret_trace, - }; - defer sema.deinit(); - - const struct_ty = sema.getStructType( - new_decl_index, - new_namespace_index, - null, - try mod.intern_pool.trackZir(gpa, file, .main_struct_inst), - ) catch |err| switch (err) { - error.OutOfMemory => return error.OutOfMemory, - // The following errors are from resolving capture values, but the root - // struct of a file has no captures. - error.AnalysisFail, - error.NeededSourceLocation, - error.GenericPoison, - error.ComptimeReturn, - error.ComptimeBreak, - => unreachable, - }; - // TODO: figure out InternPool removals for incremental compilation - //errdefer ip.remove(struct_ty); - for (comptime_mutable_decls.items) |decl_index| { - const decl = mod.declPtr(decl_index); - _ = try decl.internValue(mod); - } - - new_decl.val = Value.fromInterned(struct_ty); - new_decl.has_tv = true; - new_decl.owns_tv = true; - new_decl.analysis = .complete; - - const comp = mod.comp; - switch (comp.cache_use) { + switch (mod.comp.cache_use) { .whole => |whole| if (whole.cache_manifest) |man| { const source = file.getSource(gpa) catch |err| { try reportRetryableFileError(mod, file, "unable to load source: {s}", .{@errorName(err)}); @@ -5940,14 +5950,6 @@ pub fn atomicPtrAlignment( return .none; } -pub fn opaqueSrcLoc(mod: *Module, opaque_type: InternPool.Key.OpaqueType) SrcLoc { - return mod.declPtr(opaque_type.decl).srcLoc(mod); -} - -pub fn opaqueFullyQualifiedName(mod: *Module, opaque_type: InternPool.Key.OpaqueType) !InternPool.NullTerminatedString { - return mod.declPtr(opaque_type.decl).fullyQualifiedName(mod); -} - pub fn declFileScope(mod: *Module, decl_index: Decl.Index) *File { return mod.declPtr(decl_index).getFileScope(mod); } diff --git a/src/Sema.zig b/src/Sema.zig index e362c899c6..d4a95027c3 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -2685,7 +2685,7 @@ fn getCaptures(sema: *Sema, parent_namespace: ?InternPool.NamespaceIndex, extra_ capture.* = switch (zir_capture.unwrap()) { .inst => |inst| InternPool.CaptureValue.wrap(capture: { const air_ref = try sema.resolveInst(inst.toRef()); - if (try sema.resolveValue(air_ref)) |val| { + if (try sema.resolveValueResolveLazy(air_ref)) |val| { break :capture .{ .@"comptime" = val.toIntern() }; } break :capture .{ .runtime = sema.typeOf(air_ref).toIntern() }; @@ -2697,23 +2697,20 @@ fn getCaptures(sema: *Sema, parent_namespace: ?InternPool.NamespaceIndex, extra_ return captures; } -pub fn getStructType( +fn zirStructDecl( sema: *Sema, - decl: InternPool.DeclIndex, - namespace: InternPool.NamespaceIndex, - /// The direct parent Namespace for resolving nested capture values. - parent_namespace: ?InternPool.NamespaceIndex, - tracked_inst: InternPool.TrackedInst.Index, -) !InternPool.Index { + block: *Block, + extended: Zir.Inst.Extended.InstData, + inst: Zir.Inst.Index, +) CompileError!Air.Inst.Ref { const mod = sema.mod; const gpa = sema.gpa; const ip = &mod.intern_pool; - const zir_index = tracked_inst.resolve(ip); - const extended = sema.code.instructions.items(.data)[@intFromEnum(zir_index)].extended; - assert(extended.opcode == .struct_decl); const small: Zir.Inst.StructDecl.Small = @bitCast(extended.small); + const extra = sema.code.extraData(Zir.Inst.StructDecl, extended.operand); + const src = extra.data.src(); + var extra_index = extra.end; - var extra_index: usize = extended.operand + @typeInfo(Zir.Inst.StructDecl).Struct.fields.len; const captures_len = if (small.has_captures_len) blk: { const captures_len = sema.code.extra[extra_index]; extra_index += 1; @@ -2730,7 +2727,7 @@ pub fn getStructType( break :blk decls_len; } else 0; - const captures = try sema.getCaptures(parent_namespace, extra_index, captures_len); + const captures = try sema.getCaptures(block.namespace, extra_index, captures_len); extra_index += captures_len; if (small.has_backing_int) { @@ -2743,50 +2740,38 @@ pub fn getStructType( } } - const decls = sema.code.bodySlice(extra_index, decls_len); - try mod.scanNamespace(namespace, decls, mod.declPtr(decl)); - extra_index += decls_len; - - const ty = try ip.getStructType(gpa, .{ - .decl = decl, - .namespace = namespace.toOptional(), - .zir_index = tracked_inst.toOptional(), + const wip_ty = switch (try ip.getStructType(gpa, .{ .layout = small.layout, - .known_non_opv = small.known_non_opv, - .is_tuple = small.is_tuple, .fields_len = fields_len, + .known_non_opv = small.known_non_opv, .requires_comptime = if (small.known_comptime_only) .yes else .unknown, - .any_default_inits = small.any_default_inits, + .is_tuple = small.is_tuple, .any_comptime_fields = small.any_comptime_fields, + .any_default_inits = small.any_default_inits, .inits_resolved = false, .any_aligned_fields = small.any_aligned_fields, - .captures = captures, - }); - - return ty; -} - -fn zirStructDecl( - sema: *Sema, - block: *Block, - extended: Zir.Inst.Extended.InstData, - inst: Zir.Inst.Index, -) CompileError!Air.Inst.Ref { - const mod = sema.mod; - const ip = &mod.intern_pool; - const small: Zir.Inst.StructDecl.Small = @bitCast(extended.small); - const src = sema.code.extraData(Zir.Inst.StructDecl, extended.operand).data.src(); - - // Because these three things each reference each other, `undefined` - // placeholders are used before being set after the struct type gains an - // InternPool index. + .has_namespace = true or decls_len > 0, // TODO: see below + .key = .{ .declared = .{ + .zir_index = try ip.trackZir(gpa, block.getFileScope(mod), inst), + .captures = captures, + } }, + })) { + .existing => |ty| return Air.internedToRef(ty), + .wip => |wip| wip: { + if (sema.builtin_type_target_index == .none) break :wip wip; + var new = wip; + new.index = sema.builtin_type_target_index; + ip.resolveBuiltinType(new.index, wip.index); + break :wip new; + }, + }; + errdefer wip_ty.cancel(ip); const new_decl_index = try sema.createAnonymousDeclTypeNamed(block, src, .{ - .ty = Type.noreturn, - .val = Value.@"unreachable", + .ty = Type.type, + .val = Value.fromInterned(wip_ty.index), }, small.name_strategy, "struct", inst); - const new_decl = mod.declPtr(new_decl_index); - new_decl.owns_tv = true; + mod.declPtr(new_decl_index).owns_tv = true; errdefer mod.abortAnonDecl(new_decl_index); if (sema.mod.comp.debug_incremental) { @@ -2797,31 +2782,21 @@ fn zirStructDecl( ); } - const new_namespace_index = try mod.createNamespace(.{ + // TODO: if AstGen tells us `@This` was not used in the fields, we can elide the namespace. + const new_namespace_index: InternPool.OptionalNamespaceIndex = if (true or decls_len > 0) (try mod.createNamespace(.{ .parent = block.namespace.toOptional(), .decl_index = new_decl_index, .file_scope = block.getFileScope(mod), - }); + })).toOptional() else .none; errdefer mod.destroyNamespace(new_namespace_index); - const struct_ty = ty: { - const tracked_inst = try ip.trackZir(mod.gpa, block.getFileScope(mod), inst); - const ty = try sema.getStructType(new_decl_index, new_namespace_index, block.namespace, tracked_inst); - if (sema.builtin_type_target_index != .none) { - ip.resolveBuiltinType(sema.builtin_type_target_index, ty); - break :ty sema.builtin_type_target_index; - } - break :ty ty; - }; - // TODO: figure out InternPool removals for incremental compilation - //errdefer ip.remove(struct_ty); + if (new_namespace_index.unwrap()) |ns| { + const decls = sema.code.bodySlice(extra_index, decls_len); + try mod.scanNamespace(ns, decls, mod.declPtr(new_decl_index)); + } - new_decl.ty = Type.type; - new_decl.val = Value.fromInterned(struct_ty); - - const decl_val = sema.analyzeDeclVal(block, src, new_decl_index); try mod.finalizeAnonDecl(new_decl_index); - return decl_val; + return Air.internedToRef(wip_ty.finish(ip, new_decl_index, new_namespace_index)); } fn createAnonymousDeclTypeNamed( @@ -2931,6 +2906,7 @@ fn zirEnumDecl( const mod = sema.mod; const gpa = sema.gpa; + const ip = &mod.intern_pool; const small: Zir.Inst.EnumDecl.Small = @bitCast(extended.small); const extra = sema.code.extraData(Zir.Inst.EnumDecl, extended.operand); var extra_index: usize = extra.end; @@ -2968,39 +2944,10 @@ fn zirEnumDecl( break :blk decls_len; } else 0; - // Because these three things each reference each other, `undefined` - // placeholders are used before being set after the enum type gains an - // InternPool index. - - var done = false; - const new_decl_index = try sema.createAnonymousDeclTypeNamed(block, src, .{ - .ty = Type.noreturn, - .val = Value.@"unreachable", - }, small.name_strategy, "enum", inst); - const new_decl = mod.declPtr(new_decl_index); - new_decl.owns_tv = true; - errdefer if (!done) mod.abortAnonDecl(new_decl_index); - - if (sema.mod.comp.debug_incremental) { - try mod.intern_pool.addDependency( - sema.gpa, - InternPool.Depender.wrap(.{ .decl = new_decl_index }), - .{ .src_hash = try mod.intern_pool.trackZir(sema.gpa, block.getFileScope(mod), inst) }, - ); - } - const captures = try sema.getCaptures(block.namespace, extra_index, captures_len); extra_index += captures_len; - const new_namespace_index = try mod.createNamespace(.{ - .parent = block.namespace.toOptional(), - .decl_index = new_decl_index, - .file_scope = block.getFileScope(mod), - }); - errdefer if (!done) mod.destroyNamespace(new_namespace_index); - const decls = sema.code.bodySlice(extra_index, decls_len); - try mod.scanNamespace(new_namespace_index, decls, new_decl); extra_index += decls_len; const body = sema.code.bodySlice(extra_index, body_len); @@ -3014,36 +2961,59 @@ fn zirEnumDecl( if (bag != 0) break true; } else false; - const incomplete_enum = incomplete_enum: { - var incomplete_enum = try mod.intern_pool.getIncompleteEnum(gpa, .{ - .decl = new_decl_index, - .namespace = new_namespace_index.toOptional(), - .fields_len = fields_len, - .has_values = any_values, - .tag_mode = if (small.nonexhaustive) - .nonexhaustive - else if (tag_type_ref == .none) - .auto - else - .explicit, - .zir_index = (try mod.intern_pool.trackZir(sema.gpa, block.getFileScope(mod), inst)).toOptional(), + const wip_ty = switch (try ip.getEnumType(gpa, .{ + .has_namespace = true or decls_len > 0, // TODO: see below + .has_values = any_values, + .tag_mode = if (small.nonexhaustive) + .nonexhaustive + else if (tag_type_ref == .none) + .auto + else + .explicit, + .fields_len = fields_len, + .key = .{ .declared = .{ + .zir_index = try mod.intern_pool.trackZir(sema.gpa, block.getFileScope(mod), inst), .captures = captures, - }); - if (sema.builtin_type_target_index != .none) { - mod.intern_pool.resolveBuiltinType(sema.builtin_type_target_index, incomplete_enum.index); - incomplete_enum.index = sema.builtin_type_target_index; - } - break :incomplete_enum incomplete_enum; + } }, + })) { + .wip => |wip| wip: { + if (sema.builtin_type_target_index == .none) break :wip wip; + var new = wip; + new.index = sema.builtin_type_target_index; + ip.resolveBuiltinType(new.index, wip.index); + break :wip new; + }, + .existing => |ty| return Air.internedToRef(ty), }; - // TODO: figure out InternPool removals for incremental compilation - //errdefer if (!done) mod.intern_pool.remove(incomplete_enum.index); + errdefer wip_ty.cancel(ip); - new_decl.ty = Type.type; - new_decl.val = Value.fromInterned(incomplete_enum.index); + const new_decl_index = try sema.createAnonymousDeclTypeNamed(block, src, .{ + .ty = Type.type, + .val = Value.fromInterned(wip_ty.index), + }, small.name_strategy, "enum", inst); + const new_decl = mod.declPtr(new_decl_index); + new_decl.owns_tv = true; + errdefer mod.abortAnonDecl(new_decl_index); - const decl_val = try sema.analyzeDeclVal(block, src, new_decl_index); - try mod.finalizeAnonDecl(new_decl_index); - done = true; + if (sema.mod.comp.debug_incremental) { + try mod.intern_pool.addDependency( + sema.gpa, + InternPool.Depender.wrap(.{ .decl = new_decl_index }), + .{ .src_hash = try mod.intern_pool.trackZir(sema.gpa, block.getFileScope(mod), inst) }, + ); + } + + // TODO: if AstGen tells us `@This` was not used in the fields, we can elide the namespace. + const new_namespace_index: InternPool.OptionalNamespaceIndex = if (true or decls_len > 0) (try mod.createNamespace(.{ + .parent = block.namespace.toOptional(), + .decl_index = new_decl_index, + .file_scope = block.getFileScope(mod), + })).toOptional() else .none; + errdefer if (new_namespace_index.unwrap()) |ns| mod.destroyNamespace(ns); + + if (new_namespace_index.unwrap()) |ns| { + try mod.scanNamespace(ns, decls, new_decl); + } const int_tag_ty = ty: { // We create a block for the field type instructions because they @@ -3072,7 +3042,7 @@ fn zirEnumDecl( .parent = null, .sema = sema, .src_decl = new_decl_index, - .namespace = new_namespace_index, + .namespace = new_namespace_index.unwrap() orelse block.namespace, .instructions = .{}, .inlining = null, .is_comptime = true, @@ -3088,7 +3058,6 @@ fn zirEnumDecl( if (ty.zigTypeTag(mod) != .Int and ty.zigTypeTag(mod) != .ComptimeInt) { return sema.fail(block, tag_ty_src, "expected integer tag type, found '{}'", .{ty.fmt(sema.mod)}); } - incomplete_enum.setTagType(&mod.intern_pool, ty.toIntern()); break :ty ty; } else if (fields_len == 0) { break :ty try mod.intType(.unsigned, 0); @@ -3098,6 +3067,8 @@ fn zirEnumDecl( } }; + wip_ty.prepare(ip, new_decl_index, new_namespace_index, int_tag_ty.toIntern()); + if (small.nonexhaustive and int_tag_ty.toIntern() != .comptime_int_type) { if (fields_len > 1 and std.math.log2_int(u64, fields_len) == int_tag_ty.bitSize(mod)) { return sema.fail(block, src, "non-exhaustive enum specifies every value", .{}); @@ -3121,7 +3092,6 @@ fn zirEnumDecl( extra_index += 2; // field name, doc comment const field_name = try mod.intern_pool.getOrPutString(gpa, field_name_zir); - assert(incomplete_enum.addFieldName(&mod.intern_pool, field_name) == null); const tag_overflow = if (has_tag_value) overflow: { const tag_val_ref: Zir.Inst.Ref = @enumFromInt(sema.code.extra[extra_index]); @@ -3142,12 +3112,13 @@ fn zirEnumDecl( }; if (!(try sema.intFitsInType(last_tag_val.?, int_tag_ty, null))) break :overflow true; last_tag_val = try mod.getCoerced(last_tag_val.?, int_tag_ty); - if (incomplete_enum.addFieldValue(&mod.intern_pool, last_tag_val.?.toIntern())) |other_index| { + if (wip_ty.nextField(&mod.intern_pool, field_name, last_tag_val.?.toIntern())) |conflict| { + assert(conflict.kind == .value); // AstGen validated names are unique const value_src = mod.fieldSrcLoc(new_decl_index, .{ .index = field_i, .range = .value, }).lazy; - const other_field_src = mod.fieldSrcLoc(new_decl_index, .{ .index = other_index }).lazy; + const other_field_src = mod.fieldSrcLoc(new_decl_index, .{ .index = conflict.prev_field_idx }).lazy; const msg = msg: { const msg = try sema.errMsg(block, value_src, "enum tag value {} already taken", .{last_tag_val.?.fmtValue(int_tag_ty, sema.mod)}); errdefer msg.destroy(gpa); @@ -3164,9 +3135,10 @@ fn zirEnumDecl( else try mod.intValue(int_tag_ty, 0); if (overflow != null) break :overflow true; - if (incomplete_enum.addFieldValue(&mod.intern_pool, last_tag_val.?.toIntern())) |other_index| { + if (wip_ty.nextField(&mod.intern_pool, field_name, last_tag_val.?.toIntern())) |conflict| { + assert(conflict.kind == .value); // AstGen validated names are unique const field_src = mod.fieldSrcLoc(new_decl_index, .{ .index = field_i }).lazy; - const other_field_src = mod.fieldSrcLoc(new_decl_index, .{ .index = other_index }).lazy; + const other_field_src = mod.fieldSrcLoc(new_decl_index, .{ .index = conflict.prev_field_idx }).lazy; const msg = msg: { const msg = try sema.errMsg(block, field_src, "enum tag value {} already taken", .{last_tag_val.?.fmtValue(int_tag_ty, sema.mod)}); errdefer msg.destroy(gpa); @@ -3177,6 +3149,7 @@ fn zirEnumDecl( } break :overflow false; } else overflow: { + assert(wip_ty.nextField(&mod.intern_pool, field_name, .none) == null); last_tag_val = try mod.intValue(Type.comptime_int, field_i); if (!try sema.intFitsInType(last_tag_val.?, int_tag_ty, null)) break :overflow true; last_tag_val = try mod.getCoerced(last_tag_val.?, int_tag_ty); @@ -3194,7 +3167,9 @@ fn zirEnumDecl( return sema.failWithOwnedErrorMsg(block, msg); } } - return decl_val; + + try mod.finalizeAnonDecl(new_decl_index); + return Air.internedToRef(wip_ty.finish(ip)); } fn zirUnionDecl( @@ -3208,6 +3183,7 @@ fn zirUnionDecl( const mod = sema.mod; const gpa = sema.gpa; + const ip = &mod.intern_pool; const small: Zir.Inst.UnionDecl.Small = @bitCast(extended.small); const extra = sema.code.extraData(Zir.Inst.UnionDecl, extended.operand); var extra_index: usize = extra.end; @@ -3233,16 +3209,53 @@ fn zirUnionDecl( break :blk decls_len; } else 0; - // Because these three things each reference each other, `undefined` - // placeholders are used before being set after the union type gains an - // InternPool index. + const captures = try sema.getCaptures(block.namespace, extra_index, captures_len); + extra_index += captures_len; + + const wip_ty = switch (try ip.getUnionType(gpa, .{ + .flags = .{ + .layout = small.layout, + .status = .none, + .runtime_tag = if (small.has_tag_type or small.auto_enum_tag) + .tagged + else if (small.layout != .Auto) + .none + else switch (block.wantSafety()) { + true => .safety, + false => .none, + }, + .any_aligned_fields = small.any_aligned_fields, + .requires_comptime = .unknown, + .assumed_runtime_bits = false, + .assumed_pointer_aligned = false, + .alignment = .none, + }, + .has_namespace = true or decls_len != 0, // TODO: see below + .fields_len = fields_len, + .enum_tag_ty = .none, // set later + .field_types = &.{}, // set later + .field_aligns = &.{}, // set later + .key = .{ .declared = .{ + .zir_index = try ip.trackZir(gpa, block.getFileScope(mod), inst), + .captures = captures, + } }, + })) { + .wip => |wip| wip: { + if (sema.builtin_type_target_index == .none) break :wip wip; + var new = wip; + new.index = sema.builtin_type_target_index; + ip.resolveBuiltinType(new.index, wip.index); + break :wip new; + }, + .existing => |ty| return Air.internedToRef(ty), + }; + errdefer wip_ty.cancel(ip); const new_decl_index = try sema.createAnonymousDeclTypeNamed(block, src, .{ - .ty = Type.noreturn, - .val = Value.@"unreachable", + .ty = Type.type, + .val = Value.fromInterned(wip_ty.index), }, small.name_strategy, "union", inst); - const new_decl = mod.declPtr(new_decl_index); - new_decl.owns_tv = true; + mod.declPtr(new_decl_index).owns_tv = true; errdefer mod.abortAnonDecl(new_decl_index); if (sema.mod.comp.debug_incremental) { @@ -3253,62 +3266,22 @@ fn zirUnionDecl( ); } - const captures = try sema.getCaptures(block.namespace, extra_index, captures_len); - extra_index += captures_len; - - const new_namespace_index = try mod.createNamespace(.{ + // TODO: if AstGen tells us `@This` was not used in the fields, we can elide the namespace. + const new_namespace_index: InternPool.OptionalNamespaceIndex = if (true or decls_len > 0) (try mod.createNamespace(.{ .parent = block.namespace.toOptional(), .decl_index = new_decl_index, .file_scope = block.getFileScope(mod), - }); - errdefer mod.destroyNamespace(new_namespace_index); + })).toOptional() else .none; + errdefer if (new_namespace_index.unwrap()) |ns| mod.destroyNamespace(ns); - const union_ty = ty: { - const ty = try mod.intern_pool.getUnionType(gpa, .{ - .flags = .{ - .layout = small.layout, - .status = .none, - .runtime_tag = if (small.has_tag_type or small.auto_enum_tag) - .tagged - else if (small.layout != .Auto) - .none - else switch (block.wantSafety()) { - true => .safety, - false => .none, - }, - .any_aligned_fields = small.any_aligned_fields, - .requires_comptime = .unknown, - .assumed_runtime_bits = false, - .assumed_pointer_aligned = false, - .alignment = .none, - }, - .decl = new_decl_index, - .namespace = new_namespace_index, - .zir_index = (try mod.intern_pool.trackZir(gpa, block.getFileScope(mod), inst)).toOptional(), - .fields_len = fields_len, - .enum_tag_ty = .none, - .field_types = &.{}, - .field_aligns = &.{}, - .captures = captures, - }); - if (sema.builtin_type_target_index != .none) { - mod.intern_pool.resolveBuiltinType(sema.builtin_type_target_index, ty); - break :ty sema.builtin_type_target_index; - } - break :ty ty; - }; - // TODO: figure out InternPool removals for incremental compilation - //errdefer mod.intern_pool.remove(union_ty); + if (new_namespace_index.unwrap()) |ns| { + const decls = sema.code.bodySlice(extra_index, decls_len); + try mod.scanNamespace(ns, decls, mod.declPtr(new_decl_index)); + } - new_decl.ty = Type.type; - new_decl.val = Value.fromInterned(union_ty); - - const decls = sema.code.bodySlice(extra_index, decls_len); - try mod.scanNamespace(new_namespace_index, decls, new_decl); - - const decl_val = sema.analyzeDeclVal(block, src, new_decl_index); try mod.finalizeAnonDecl(new_decl_index); - return decl_val; + + return Air.internedToRef(wip_ty.finish(ip, new_decl_index, new_namespace_index)); } fn zirOpaqueDecl( @@ -3321,6 +3294,9 @@ fn zirOpaqueDecl( defer tracy.end(); const mod = sema.mod; + const gpa = sema.gpa; + const ip = &mod.intern_pool; + const small: Zir.Inst.OpaqueDecl.Small = @bitCast(extended.small); const extra = sema.code.extraData(Zir.Inst.OpaqueDecl, extended.operand); var extra_index: usize = extra.end; @@ -3339,54 +3315,51 @@ fn zirOpaqueDecl( break :blk decls_len; } else 0; - // Because these three things each reference each other, `undefined` - // placeholders are used in two places before being set after the opaque - // type gains an InternPool index. - - const new_decl_index = try sema.createAnonymousDeclTypeNamed(block, src, .{ - .ty = Type.noreturn, - .val = Value.@"unreachable", - }, small.name_strategy, "opaque", inst); - const new_decl = mod.declPtr(new_decl_index); - new_decl.owns_tv = true; - errdefer mod.abortAnonDecl(new_decl_index); - - if (sema.mod.comp.debug_incremental) { - try mod.intern_pool.addDependency( - sema.gpa, - InternPool.Depender.wrap(.{ .decl = new_decl_index }), - .{ .src_hash = try mod.intern_pool.trackZir(sema.gpa, block.getFileScope(mod), inst) }, - ); - } - const captures = try sema.getCaptures(block.namespace, extra_index, captures_len); extra_index += captures_len; - const new_namespace_index = try mod.createNamespace(.{ + const wip_ty = switch (try ip.getOpaqueType(gpa, .{ + .has_namespace = decls_len != 0, + .key = .{ .declared = .{ + .zir_index = try ip.trackZir(gpa, block.getFileScope(mod), inst), + .captures = captures, + } }, + })) { + .wip => |wip| wip, + .existing => |ty| return Air.internedToRef(ty), + }; + errdefer wip_ty.cancel(ip); + + const new_decl_index = try sema.createAnonymousDeclTypeNamed(block, src, .{ + .ty = Type.type, + .val = Value.fromInterned(wip_ty.index), + }, small.name_strategy, "opaque", inst); + mod.declPtr(new_decl_index).owns_tv = true; + errdefer mod.abortAnonDecl(new_decl_index); + + if (sema.mod.comp.debug_incremental) { + try ip.addDependency( + gpa, + InternPool.Depender.wrap(.{ .decl = new_decl_index }), + .{ .src_hash = try ip.trackZir(gpa, block.getFileScope(mod), inst) }, + ); + } + + const new_namespace_index: InternPool.OptionalNamespaceIndex = if (decls_len > 0) (try mod.createNamespace(.{ .parent = block.namespace.toOptional(), .decl_index = new_decl_index, .file_scope = block.getFileScope(mod), - }); - errdefer mod.destroyNamespace(new_namespace_index); + })).toOptional() else .none; + errdefer if (new_namespace_index.unwrap()) |ns| mod.destroyNamespace(ns); - const opaque_ty = try mod.intern_pool.getOpaqueType(sema.gpa, .{ - .decl = new_decl_index, - .namespace = new_namespace_index, - .zir_index = (try mod.intern_pool.trackZir(sema.gpa, block.getFileScope(mod), inst)).toOptional(), - .captures = captures, - }); - // TODO: figure out InternPool removals for incremental compilation - //errdefer mod.intern_pool.remove(opaque_ty); + if (new_namespace_index.unwrap()) |ns| { + const decls = sema.code.bodySlice(extra_index, decls_len); + try mod.scanNamespace(ns, decls, mod.declPtr(new_decl_index)); + } - new_decl.ty = Type.type; - new_decl.val = Value.fromInterned(opaque_ty); - - const decls = sema.code.bodySlice(extra_index, decls_len); - try mod.scanNamespace(new_namespace_index, decls, new_decl); - - const decl_val = sema.analyzeDeclVal(block, src, new_decl_index); try mod.finalizeAnonDecl(new_decl_index); - return decl_val; + + return Air.internedToRef(wip_ty.finish(ip, new_decl_index, new_namespace_index)); } fn zirErrorSetDecl( @@ -21322,103 +21295,11 @@ fn zirReify( try ip.getOrPutString(gpa, "is_exhaustive"), ).?); - // Decls if (decls_val.sliceLen(mod) > 0) { return sema.fail(block, src, "reified enums must have no decls", .{}); } - const int_tag_ty = tag_type_val.toType(); - if (int_tag_ty.zigTypeTag(mod) != .Int) { - return sema.fail(block, src, "Type.Enum.tag_type must be an integer type", .{}); - } - - // Because these things each reference each other, `undefined` - // placeholders are used before being set after the enum type gains - // an InternPool index. - - const new_decl_index = try sema.createAnonymousDeclTypeNamed(block, src, .{ - .ty = Type.noreturn, - .val = Value.@"unreachable", - }, name_strategy, "enum", inst); - const new_decl = mod.declPtr(new_decl_index); - new_decl.owns_tv = true; - errdefer { - new_decl.has_tv = false; // namespace and val were destroyed by later errdefers - mod.abortAnonDecl(new_decl_index); - } - - // Define our empty enum decl - const fields_len: u32 = @intCast(try sema.usizeCast(block, src, fields_val.sliceLen(mod))); - const incomplete_enum = try ip.getIncompleteEnum(gpa, .{ - .decl = new_decl_index, - .namespace = .none, - .fields_len = fields_len, - .has_values = true, - .tag_mode = if (!is_exhaustive_val.toBool()) - .nonexhaustive - else - .explicit, - .tag_ty = int_tag_ty.toIntern(), - .zir_index = .none, - .captures = &.{}, - }); - // TODO: figure out InternPool removals for incremental compilation - //errdefer ip.remove(incomplete_enum.index); - - new_decl.ty = Type.type; - new_decl.val = Value.fromInterned(incomplete_enum.index); - - for (0..fields_len) |field_i| { - const elem_val = try fields_val.elemValue(mod, field_i); - const elem_struct_type = ip.loadStructType(ip.typeOf(elem_val.toIntern())); - const name_val = try elem_val.fieldValue(mod, elem_struct_type.nameIndex( - ip, - try ip.getOrPutString(gpa, "name"), - ).?); - const value_val = try elem_val.fieldValue(mod, elem_struct_type.nameIndex( - ip, - try ip.getOrPutString(gpa, "value"), - ).?); - - const field_name = try name_val.toIpString(Type.slice_const_u8, mod); - - if (!try sema.intFitsInType(value_val, int_tag_ty, null)) { - // TODO: better source location - return sema.fail(block, src, "field '{}' with enumeration value '{}' is too large for backing int type '{}'", .{ - field_name.fmt(ip), - value_val.fmtValue(Type.comptime_int, mod), - int_tag_ty.fmt(mod), - }); - } - - if (incomplete_enum.addFieldName(ip, field_name)) |other_index| { - const msg = msg: { - const msg = try sema.errMsg(block, src, "duplicate enum field '{}'", .{ - field_name.fmt(ip), - }); - errdefer msg.destroy(gpa); - _ = other_index; // TODO: this note is incorrect - try sema.errNote(block, src, msg, "other field here", .{}); - break :msg msg; - }; - return sema.failWithOwnedErrorMsg(block, msg); - } - - if (incomplete_enum.addFieldValue(ip, (try mod.getCoerced(value_val, int_tag_ty)).toIntern())) |other| { - const msg = msg: { - const msg = try sema.errMsg(block, src, "enum tag value {} already taken", .{value_val.fmtValue(Type.comptime_int, mod)}); - errdefer msg.destroy(gpa); - _ = other; // TODO: this note is incorrect - try sema.errNote(block, src, msg, "other enum tag value here", .{}); - break :msg msg; - }; - return sema.failWithOwnedErrorMsg(block, msg); - } - } - - const decl_val = sema.analyzeDeclVal(block, src, new_decl_index); - try mod.finalizeAnonDecl(new_decl_index); - return decl_val; + return sema.reifyEnum(block, inst, src, tag_type_val.toType(), is_exhaustive_val.toBool(), fields_val, name_strategy); }, .Opaque => { const struct_type = ip.loadStructType(ip.typeOf(union_val.val)); @@ -21432,43 +21313,27 @@ fn zirReify( return sema.fail(block, src, "reified opaque must have no decls", .{}); } - // Because these three things each reference each other, - // `undefined` placeholders are used in two places before being set - // after the opaque type gains an InternPool index. + const wip_ty = switch (try ip.getOpaqueType(gpa, .{ + .has_namespace = false, + .key = .{ .reified = .{ + .zir_index = try ip.trackZir(gpa, block.getFileScope(mod), inst), + } }, + })) { + .existing => |ty| return Air.internedToRef(ty), + .wip => |wip| wip, + }; + errdefer wip_ty.cancel(ip); const new_decl_index = try sema.createAnonymousDeclTypeNamed(block, src, .{ - .ty = Type.noreturn, - .val = Value.@"unreachable", + .ty = Type.type, + .val = Value.fromInterned(wip_ty.index), }, name_strategy, "opaque", inst); - const new_decl = mod.declPtr(new_decl_index); - new_decl.owns_tv = true; - errdefer { - new_decl.has_tv = false; // namespace and val were destroyed by later errdefers - mod.abortAnonDecl(new_decl_index); - } + mod.declPtr(new_decl_index).owns_tv = true; + errdefer mod.abortAnonDecl(new_decl_index); - const new_namespace_index = try mod.createNamespace(.{ - .parent = block.namespace.toOptional(), - .decl_index = new_decl_index, - .file_scope = block.getFileScope(mod), - }); - errdefer mod.destroyNamespace(new_namespace_index); - - const opaque_ty = try ip.getOpaqueType(gpa, .{ - .decl = new_decl_index, - .namespace = new_namespace_index, - .zir_index = .none, - .captures = &.{}, - }); - // TODO: figure out InternPool removals for incremental compilation - //errdefer ip.remove(opaque_ty); - - new_decl.ty = Type.type; - new_decl.val = Value.fromInterned(opaque_ty); - - const decl_val = sema.analyzeDeclVal(block, src, new_decl_index); try mod.finalizeAnonDecl(new_decl_index); - return decl_val; + + return Air.internedToRef(wip_ty.finish(ip, new_decl_index, .none)); }, .Union => { const struct_type = ip.loadStructType(ip.typeOf(union_val.val)); @@ -21489,214 +21354,12 @@ fn zirReify( try ip.getOrPutString(gpa, "decls"), ).?); - // Decls if (decls_val.sliceLen(mod) > 0) { return sema.fail(block, src, "reified unions must have no decls", .{}); } const layout = mod.toEnum(std.builtin.Type.ContainerLayout, layout_val); - const fields_len: u32 = @intCast(try sema.usizeCast(block, src, fields_val.sliceLen(mod))); - // Tag type - var explicit_tags_seen: []bool = &.{}; - var enum_field_names: []InternPool.NullTerminatedString = &.{}; - var enum_tag_ty: InternPool.Index = .none; - if (tag_type_val.optionalValue(mod)) |payload_val| { - enum_tag_ty = payload_val.toType().toIntern(); - - const enum_type = switch (ip.indexToKey(enum_tag_ty)) { - .enum_type => ip.loadEnumType(enum_tag_ty), - else => return sema.fail(block, src, "Type.Union.tag_type must be an enum type", .{}), - }; - - explicit_tags_seen = try sema.arena.alloc(bool, enum_type.names.len); - @memset(explicit_tags_seen, false); - } else { - enum_field_names = try sema.arena.alloc(InternPool.NullTerminatedString, fields_len); - } - - // Fields - var any_aligned_fields: bool = false; - var union_fields: std.MultiArrayList(struct { - type: InternPool.Index, - alignment: InternPool.Alignment, - }) = .{}; - var field_name_table: std.AutoArrayHashMapUnmanaged(InternPool.NullTerminatedString, void) = .{}; - try field_name_table.ensureTotalCapacity(sema.arena, fields_len); - - for (0..fields_len) |i| { - const elem_val = try fields_val.elemValue(mod, i); - const elem_struct_type = ip.loadStructType(ip.typeOf(elem_val.toIntern())); - const name_val = try elem_val.fieldValue(mod, elem_struct_type.nameIndex( - ip, - try ip.getOrPutString(gpa, "name"), - ).?); - const type_val = try elem_val.fieldValue(mod, elem_struct_type.nameIndex( - ip, - try ip.getOrPutString(gpa, "type"), - ).?); - const alignment_val = try elem_val.fieldValue(mod, elem_struct_type.nameIndex( - ip, - try ip.getOrPutString(gpa, "alignment"), - ).?); - - const field_name = try name_val.toIpString(Type.slice_const_u8, mod); - - if (enum_field_names.len != 0) { - enum_field_names[i] = field_name; - } - - if (enum_tag_ty != .none) { - const tag_info = ip.loadEnumType(enum_tag_ty); - const enum_index = tag_info.nameIndex(ip, field_name) orelse { - return sema.fail(block, src, "no field named '{}' in enum '{}'", .{ - field_name.fmt(ip), Type.fromInterned(enum_tag_ty).fmt(mod), - }); - }; - assert(explicit_tags_seen.len == tag_info.names.len); - // No check for duplicate because the check already happened in order - // to create the enum type in the first place. - assert(!explicit_tags_seen[enum_index]); - explicit_tags_seen[enum_index] = true; - } - - const gop = field_name_table.getOrPutAssumeCapacity(field_name); - if (gop.found_existing) { - // TODO: better source location - return sema.fail(block, src, "duplicate union field {}", .{field_name.fmt(ip)}); - } - - const field_ty = type_val.toType(); - const alignment_val_int = (try alignment_val.getUnsignedIntAdvanced(mod, sema)).?; - if (alignment_val_int > 0 and !math.isPowerOfTwo(alignment_val_int)) { - // TODO: better source location - return sema.fail(block, src, "alignment value '{d}' is not a power of two or zero", .{ - alignment_val_int, - }); - } - const field_align = Alignment.fromByteUnits(alignment_val_int); - any_aligned_fields = any_aligned_fields or field_align != .none; - - try union_fields.append(sema.arena, .{ - .type = field_ty.toIntern(), - .alignment = field_align, - }); - - if (field_ty.zigTypeTag(mod) == .Opaque) { - const msg = msg: { - const msg = try sema.errMsg(block, src, "opaque types have unknown size and therefore cannot be directly embedded in unions", .{}); - errdefer msg.destroy(gpa); - - try sema.addDeclaredHereNote(msg, field_ty); - break :msg msg; - }; - return sema.failWithOwnedErrorMsg(block, msg); - } - if (layout == .Extern and !try sema.validateExternType(field_ty, .union_field)) { - const msg = msg: { - const msg = try sema.errMsg(block, src, "extern unions cannot contain fields of type '{}'", .{field_ty.fmt(mod)}); - errdefer msg.destroy(gpa); - - const src_decl = mod.declPtr(block.src_decl); - try sema.explainWhyTypeIsNotExtern(msg, src_decl.toSrcLoc(src, mod), field_ty, .union_field); - - try sema.addDeclaredHereNote(msg, field_ty); - break :msg msg; - }; - return sema.failWithOwnedErrorMsg(block, msg); - } else if (layout == .Packed and !try sema.validatePackedType(field_ty)) { - const msg = msg: { - const msg = try sema.errMsg(block, src, "packed unions cannot contain fields of type '{}'", .{field_ty.fmt(mod)}); - errdefer msg.destroy(gpa); - - const src_decl = mod.declPtr(block.src_decl); - try sema.explainWhyTypeIsNotPacked(msg, src_decl.toSrcLoc(src, mod), field_ty); - - try sema.addDeclaredHereNote(msg, field_ty); - break :msg msg; - }; - return sema.failWithOwnedErrorMsg(block, msg); - } - } - - if (enum_tag_ty != .none) { - const tag_info = ip.loadEnumType(enum_tag_ty); - if (tag_info.names.len > fields_len) { - const msg = msg: { - const msg = try sema.errMsg(block, src, "enum field(s) missing in union", .{}); - errdefer msg.destroy(gpa); - - assert(explicit_tags_seen.len == tag_info.names.len); - for (tag_info.names.get(ip), 0..) |field_name, field_index| { - if (explicit_tags_seen[field_index]) continue; - try sema.addFieldErrNote(Type.fromInterned(enum_tag_ty), field_index, msg, "field '{}' missing, declared here", .{ - field_name.fmt(ip), - }); - } - try sema.addDeclaredHereNote(msg, Type.fromInterned(enum_tag_ty)); - break :msg msg; - }; - return sema.failWithOwnedErrorMsg(block, msg); - } - } else { - enum_tag_ty = try sema.generateUnionTagTypeSimple(block, enum_field_names, .none); - } - - // Because these three things each reference each other, `undefined` - // placeholders are used before being set after the union type gains an - // InternPool index. - - const new_decl_index = try sema.createAnonymousDeclTypeNamed(block, src, .{ - .ty = Type.noreturn, - .val = Value.@"unreachable", - }, name_strategy, "union", inst); - const new_decl = mod.declPtr(new_decl_index); - new_decl.owns_tv = true; - errdefer { - new_decl.has_tv = false; // namespace and val were destroyed by later errdefers - mod.abortAnonDecl(new_decl_index); - } - - const new_namespace_index = try mod.createNamespace(.{ - .parent = block.namespace.toOptional(), - .decl_index = new_decl_index, - .file_scope = block.getFileScope(mod), - }); - errdefer mod.destroyNamespace(new_namespace_index); - - const union_ty = try ip.getUnionType(gpa, .{ - .decl = new_decl_index, - .namespace = new_namespace_index, - .enum_tag_ty = enum_tag_ty, - .fields_len = fields_len, - .zir_index = .none, - .flags = .{ - .layout = layout, - .status = .have_field_types, - .runtime_tag = if (!tag_type_val.isNull(mod)) - .tagged - else if (layout != .Auto) - .none - else switch (block.wantSafety()) { - true => .safety, - false => .none, - }, - .any_aligned_fields = any_aligned_fields, - .requires_comptime = .unknown, - .assumed_runtime_bits = false, - .assumed_pointer_aligned = false, - .alignment = .none, - }, - .field_types = union_fields.items(.type), - .field_aligns = if (any_aligned_fields) union_fields.items(.alignment) else &.{}, - .captures = &.{}, - }); - - new_decl.ty = Type.type; - new_decl.val = Value.fromInterned(union_ty); - - const decl_val = sema.analyzeDeclVal(block, src, new_decl_index); - try mod.finalizeAnonDecl(new_decl_index); - return decl_val; + return sema.reifyUnion(block, inst, src, layout, tag_type_val, fields_val, name_strategy); }, .Fn => { const struct_type = ip.loadStructType(ip.typeOf(union_val.val)); @@ -21795,13 +21458,363 @@ fn zirReify( } } +fn reifyEnum( + sema: *Sema, + block: *Block, + inst: Zir.Inst.Index, + src: LazySrcLoc, + tag_ty: Type, + is_exhaustive: bool, + fields_val: Value, + name_strategy: Zir.Inst.NameStrategy, +) CompileError!Air.Inst.Ref { + const mod = sema.mod; + const gpa = sema.gpa; + const ip = &mod.intern_pool; + + // This logic must stay in sync with the structure of `std.builtin.Type.Enum` - search for `fieldValue`. + + const fields_len: u32 = @intCast(fields_val.sliceLen(mod)); + + // The validation work here is non-trivial, and it's possible the type already exists. + // So in this first pass, let's just construct a hash to optimize for this case. If the + // inputs turn out to be invalid, we can cancel the WIP type later. + + // For deduplication purposes, we must create a hash including all details of this type. + // TODO: use a longer hash! + var hasher = std.hash.Wyhash.init(0); + std.hash.autoHash(&hasher, tag_ty.toIntern()); + std.hash.autoHash(&hasher, is_exhaustive); + std.hash.autoHash(&hasher, fields_len); + + for (0..fields_len) |field_idx| { + const field_info = try fields_val.elemValue(mod, field_idx); + + const field_name_val = try field_info.fieldValue(mod, 0); + const field_value_val = try sema.resolveLazyValue(try field_info.fieldValue(mod, 1)); + + const field_name = try field_name_val.toIpString(Type.slice_const_u8, mod); + + std.hash.autoHash(&hasher, .{ + field_name, + field_value_val.toIntern(), + }); + } + + const wip_ty = switch (try ip.getEnumType(gpa, .{ + .has_namespace = false, + .has_values = true, + .tag_mode = if (is_exhaustive) .explicit else .nonexhaustive, + .fields_len = fields_len, + .key = .{ .reified = .{ + .zir_index = try ip.trackZir(gpa, block.getFileScope(mod), inst), + .type_hash = hasher.final(), + } }, + })) { + .wip => |wip| wip, + .existing => |ty| return Air.internedToRef(ty), + }; + errdefer wip_ty.cancel(ip); + + if (tag_ty.zigTypeTag(mod) != .Int) { + return sema.fail(block, src, "Type.Enum.tag_type must be an integer type", .{}); + } + + const new_decl_index = try sema.createAnonymousDeclTypeNamed(block, src, .{ + .ty = Type.type, + .val = Value.fromInterned(wip_ty.index), + }, name_strategy, "enum", inst); + mod.declPtr(new_decl_index).owns_tv = true; + errdefer mod.abortAnonDecl(new_decl_index); + + wip_ty.prepare(ip, new_decl_index, .none, tag_ty.toIntern()); + + for (0..fields_len) |field_idx| { + const field_info = try fields_val.elemValue(mod, field_idx); + + const field_name_val = try field_info.fieldValue(mod, 0); + const field_value_val = try sema.resolveLazyValue(try field_info.fieldValue(mod, 1)); + + const field_name = try field_name_val.toIpString(Type.slice_const_u8, mod); + + if (!try sema.intFitsInType(field_value_val, tag_ty, null)) { + // TODO: better source location + return sema.fail(block, src, "field '{}' with enumeration value '{}' is too large for backing int type '{}'", .{ + field_name.fmt(ip), + field_value_val.fmtValue(Type.comptime_int, mod), + tag_ty.fmt(mod), + }); + } + + const coerced_field_val = try mod.getCoerced(field_value_val, tag_ty); + if (wip_ty.nextField(ip, field_name, coerced_field_val.toIntern())) |conflict| { + return sema.failWithOwnedErrorMsg(block, switch (conflict.kind) { + .name => msg: { + const msg = try sema.errMsg(block, src, "duplicate enum field '{}'", .{field_name.fmt(ip)}); + errdefer msg.destroy(gpa); + _ = conflict.prev_field_idx; // TODO: this note is incorrect + try sema.errNote(block, src, msg, "other field here", .{}); + break :msg msg; + }, + .value => msg: { + const msg = try sema.errMsg(block, src, "enum tag value {} already taken", .{field_value_val.fmtValue(Type.comptime_int, mod)}); + errdefer msg.destroy(gpa); + _ = conflict.prev_field_idx; // TODO: this note is incorrect + try sema.errNote(block, src, msg, "other enum tag value here", .{}); + break :msg msg; + }, + }); + } + } + + if (!is_exhaustive and fields_len > 1 and std.math.log2_int(u64, fields_len) == tag_ty.bitSize(mod)) { + return sema.fail(block, src, "non-exhaustive enum specified every value", .{}); + } + + try mod.finalizeAnonDecl(new_decl_index); + return Air.internedToRef(wip_ty.finish(ip)); +} + +fn reifyUnion( + sema: *Sema, + block: *Block, + inst: Zir.Inst.Index, + src: LazySrcLoc, + layout: std.builtin.Type.ContainerLayout, + opt_tag_type_val: Value, + fields_val: Value, + name_strategy: Zir.Inst.NameStrategy, +) CompileError!Air.Inst.Ref { + const mod = sema.mod; + const gpa = sema.gpa; + const ip = &mod.intern_pool; + + // This logic must stay in sync with the structure of `std.builtin.Type.Union` - search for `fieldValue`. + + const fields_len: u32 = @intCast(fields_val.sliceLen(mod)); + + // The validation work here is non-trivial, and it's possible the type already exists. + // So in this first pass, let's just construct a hash to optimize for this case. If the + // inputs turn out to be invalid, we can cancel the WIP type later. + + // For deduplication purposes, we must create a hash including all details of this type. + // TODO: use a longer hash! + var hasher = std.hash.Wyhash.init(0); + std.hash.autoHash(&hasher, layout); + std.hash.autoHash(&hasher, opt_tag_type_val.toIntern()); + std.hash.autoHash(&hasher, fields_len); + + var any_aligns = false; + + for (0..fields_len) |field_idx| { + const field_info = try fields_val.elemValue(mod, field_idx); + + const field_name_val = try field_info.fieldValue(mod, 0); + const field_type_val = try field_info.fieldValue(mod, 1); + const field_align_val = try sema.resolveLazyValue(try field_info.fieldValue(mod, 2)); + + const field_name = try field_name_val.toIpString(Type.slice_const_u8, mod); + + std.hash.autoHash(&hasher, .{ + field_name, + field_type_val.toIntern(), + field_align_val.toIntern(), + }); + + if (field_align_val.toUnsignedInt(mod) != 0) { + any_aligns = true; + } + } + + const wip_ty = switch (try ip.getUnionType(gpa, .{ + .flags = .{ + .layout = layout, + .status = .none, + .runtime_tag = if (opt_tag_type_val.optionalValue(mod) != null) + .tagged + else if (layout != .Auto) + .none + else switch (block.wantSafety()) { + true => .safety, + false => .none, + }, + .any_aligned_fields = any_aligns, + .requires_comptime = .unknown, + .assumed_runtime_bits = false, + .assumed_pointer_aligned = false, + .alignment = .none, + }, + .has_namespace = false, + .fields_len = fields_len, + .enum_tag_ty = .none, // set later because not yet validated + .field_types = &.{}, // set later + .field_aligns = &.{}, // set later + .key = .{ .reified = .{ + .zir_index = try ip.trackZir(gpa, block.getFileScope(mod), inst), + .type_hash = hasher.final(), + } }, + })) { + .wip => |wip| wip, + .existing => |ty| return Air.internedToRef(ty), + }; + errdefer wip_ty.cancel(ip); + + const new_decl_index = try sema.createAnonymousDeclTypeNamed(block, src, .{ + .ty = Type.type, + .val = Value.fromInterned(wip_ty.index), + }, name_strategy, "union", inst); + mod.declPtr(new_decl_index).owns_tv = true; + errdefer mod.abortAnonDecl(new_decl_index); + + const field_types = try sema.arena.alloc(InternPool.Index, fields_len); + const field_aligns = if (any_aligns) try sema.arena.alloc(InternPool.Alignment, fields_len) else undefined; + + const enum_tag_ty, const has_explicit_tag = if (opt_tag_type_val.optionalValue(mod)) |tag_type_val| tag_ty: { + switch (ip.indexToKey(tag_type_val.toIntern())) { + .enum_type => {}, + else => return sema.fail(block, src, "Type.Union.tag_type must be an enum type", .{}), + } + const enum_tag_ty = tag_type_val.toType(); + + // We simply track which fields of the tag type have been seen. + const tag_ty_fields_len = enum_tag_ty.enumFieldCount(mod); + var seen_tags = try std.DynamicBitSetUnmanaged.initEmpty(sema.arena, tag_ty_fields_len); + + for (field_types, 0..) |*field_ty, field_idx| { + const field_info = try fields_val.elemValue(mod, field_idx); + + const field_name_val = try field_info.fieldValue(mod, 0); + const field_type_val = try field_info.fieldValue(mod, 1); + + const field_name = try field_name_val.toIpString(Type.slice_const_u8, mod); + + const enum_index = enum_tag_ty.enumFieldIndex(field_name, mod) orelse { + // TODO: better source location + return sema.fail(block, src, "no field named '{}' in enum '{}'", .{ + field_name.fmt(ip), enum_tag_ty.fmt(mod), + }); + }; + if (seen_tags.isSet(enum_index)) { + // TODO: better source location + return sema.fail(block, src, "duplicate union field {}", .{field_name.fmt(ip)}); + } + seen_tags.set(enum_index); + + field_ty.* = field_type_val.toIntern(); + if (any_aligns) { + const byte_align = try (try field_info.fieldValue(mod, 2)).toUnsignedIntAdvanced(sema); + if (byte_align > 0 and !math.isPowerOfTwo(byte_align)) { + // TODO: better source location + return sema.fail(block, src, "alignment value '{d}' is not a power of two or zero", .{byte_align}); + } + field_aligns[field_idx] = Alignment.fromByteUnits(byte_align); + } + } + + if (tag_ty_fields_len > fields_len) return sema.failWithOwnedErrorMsg(block, msg: { + const msg = try sema.errMsg(block, src, "enum fields missing in union", .{}); + errdefer msg.destroy(gpa); + var it = seen_tags.iterator(.{ .kind = .unset }); + while (it.next()) |enum_index| { + const field_name = enum_tag_ty.enumFieldName(enum_index, mod); + try sema.addFieldErrNote(enum_tag_ty, enum_index, msg, "field '{}' missing, declared here", .{ + field_name.fmt(ip), + }); + } + try sema.addDeclaredHereNote(msg, enum_tag_ty); + break :msg msg; + }); + + break :tag_ty .{ enum_tag_ty.toIntern(), true }; + } else tag_ty: { + // We must track field names and set up the tag type ourselves. + var field_names: std.AutoArrayHashMapUnmanaged(InternPool.NullTerminatedString, void) = .{}; + try field_names.ensureTotalCapacity(sema.arena, fields_len); + + for (field_types, 0..) |*field_ty, field_idx| { + const field_info = try fields_val.elemValue(mod, field_idx); + + const field_name_val = try field_info.fieldValue(mod, 0); + const field_type_val = try field_info.fieldValue(mod, 1); + + const field_name = try field_name_val.toIpString(Type.slice_const_u8, mod); + const gop = field_names.getOrPutAssumeCapacity(field_name); + if (gop.found_existing) { + // TODO: better source location + return sema.fail(block, src, "duplicate union field {}", .{field_name.fmt(ip)}); + } + + field_ty.* = field_type_val.toIntern(); + if (any_aligns) { + const byte_align = try (try field_info.fieldValue(mod, 2)).toUnsignedIntAdvanced(sema); + if (byte_align > 0 and !math.isPowerOfTwo(byte_align)) { + // TODO: better source location + return sema.fail(block, src, "alignment value '{d}' is not a power of two or zero", .{byte_align}); + } + field_aligns[field_idx] = Alignment.fromByteUnits(byte_align); + } + } + + const enum_tag_ty = try sema.generateUnionTagTypeSimple(block, field_names.keys(), mod.declPtr(new_decl_index)); + break :tag_ty .{ enum_tag_ty, false }; + }; + errdefer if (!has_explicit_tag) ip.remove(enum_tag_ty); // remove generated tag type on error + + for (field_types) |field_ty_ip| { + const field_ty = Type.fromInterned(field_ty_ip); + if (field_ty.zigTypeTag(mod) == .Opaque) { + return sema.failWithOwnedErrorMsg(block, msg: { + const msg = try sema.errMsg(block, src, "opaque types have unknown size and therefore cannot be directly embedded in unions", .{}); + errdefer msg.destroy(gpa); + + try sema.addDeclaredHereNote(msg, field_ty); + break :msg msg; + }); + } + if (layout == .Extern and !try sema.validateExternType(field_ty, .union_field)) { + return sema.failWithOwnedErrorMsg(block, msg: { + const msg = try sema.errMsg(block, src, "extern unions cannot contain fields of type '{}'", .{field_ty.fmt(mod)}); + errdefer msg.destroy(gpa); + + const src_decl = mod.declPtr(block.src_decl); + try sema.explainWhyTypeIsNotExtern(msg, src.toSrcLoc(src_decl, mod), field_ty, .union_field); + + try sema.addDeclaredHereNote(msg, field_ty); + break :msg msg; + }); + } else if (layout == .Packed and !try sema.validatePackedType(field_ty)) { + return sema.failWithOwnedErrorMsg(block, msg: { + const msg = try sema.errMsg(block, src, "packed unions cannot contain fields of type '{}'", .{field_ty.fmt(mod)}); + errdefer msg.destroy(gpa); + + const src_decl = mod.declPtr(block.src_decl); + try sema.explainWhyTypeIsNotPacked(msg, src.toSrcLoc(src_decl, mod), field_ty); + + try sema.addDeclaredHereNote(msg, field_ty); + break :msg msg; + }); + } + } + + const loaded_union = ip.loadUnionType(wip_ty.index); + loaded_union.setFieldTypes(ip, field_types); + if (any_aligns) { + loaded_union.setFieldAligns(ip, field_aligns); + } + loaded_union.tagTypePtr(ip).* = enum_tag_ty; + loaded_union.flagsPtr(ip).status = .have_field_types; + + try mod.finalizeAnonDecl(new_decl_index); + return Air.internedToRef(wip_ty.finish(ip, new_decl_index, .none)); +} + fn reifyStruct( sema: *Sema, block: *Block, inst: Zir.Inst.Index, src: LazySrcLoc, layout: std.builtin.Type.ContainerLayout, - backing_int_val: Value, + opt_backing_int_val: Value, fields_val: Value, name_strategy: Zir.Inst.NameStrategy, is_tuple: bool, @@ -21810,112 +21823,126 @@ fn reifyStruct( const gpa = sema.gpa; const ip = &mod.intern_pool; + // This logic must stay in sync with the structure of `std.builtin.Type.Struct` - search for `fieldValue`. + + const fields_len: u32 = @intCast(fields_val.sliceLen(mod)); + + // The validation work here is non-trivial, and it's possible the type already exists. + // So in this first pass, let's just construct a hash to optimize for this case. If the + // inputs turn out to be invalid, we can cancel the WIP type later. + + // For deduplication purposes, we must create a hash including all details of this type. + // TODO: use a longer hash! + var hasher = std.hash.Wyhash.init(0); + std.hash.autoHash(&hasher, layout); + std.hash.autoHash(&hasher, opt_backing_int_val.toIntern()); + std.hash.autoHash(&hasher, is_tuple); + std.hash.autoHash(&hasher, fields_len); + + var any_comptime_fields = false; + var any_default_inits = false; + var any_aligned_fields = false; + + for (0..fields_len) |field_idx| { + const field_info = try fields_val.elemValue(mod, field_idx); + + const field_name_val = try field_info.fieldValue(mod, 0); + const field_type_val = try field_info.fieldValue(mod, 1); + const field_default_value_val = try field_info.fieldValue(mod, 2); + const field_is_comptime_val = try field_info.fieldValue(mod, 3); + const field_alignment_val = try sema.resolveLazyValue(try field_info.fieldValue(mod, 4)); + + const field_name = try field_name_val.toIpString(Type.slice_const_u8, mod); + const field_is_comptime = field_is_comptime_val.toBool(); + const field_default_value: InternPool.Index = if (field_default_value_val.optionalValue(mod)) |ptr_val| d: { + const ptr_ty = try mod.singleConstPtrType(field_type_val.toType()); + // We need to do this deref here, so we won't check for this error case later on. + const val = try sema.pointerDeref(block, src, ptr_val, ptr_ty) orelse return sema.failWithNeededComptime( + block, + src, + .{ .needed_comptime_reason = "struct field default value must be comptime-known" }, + ); + // Resolve the value so that lazy values do not create distinct types. + break :d (try sema.resolveLazyValue(val)).toIntern(); + } else .none; + + std.hash.autoHash(&hasher, .{ + field_name, + field_type_val.toIntern(), + field_default_value, + field_is_comptime, + field_alignment_val.toIntern(), + }); + + if (field_is_comptime) any_comptime_fields = true; + if (field_default_value != .none) any_default_inits = true; + switch (try field_alignment_val.orderAgainstZeroAdvanced(mod, sema)) { + .eq => {}, + .gt => any_aligned_fields = true, + .lt => unreachable, + } + } + + const wip_ty = switch (try ip.getStructType(gpa, .{ + .layout = layout, + .fields_len = fields_len, + .known_non_opv = false, + .requires_comptime = .unknown, + .is_tuple = is_tuple, + .any_comptime_fields = any_comptime_fields, + .any_default_inits = any_default_inits, + .any_aligned_fields = any_aligned_fields, + .inits_resolved = true, + .has_namespace = false, + .key = .{ .reified = .{ + .zir_index = try ip.trackZir(gpa, block.getFileScope(mod), inst), + .type_hash = hasher.final(), + } }, + })) { + .wip => |wip| wip, + .existing => |ty| return Air.internedToRef(ty), + }; + errdefer wip_ty.cancel(ip); + if (is_tuple) switch (layout) { .Extern => return sema.fail(block, src, "extern tuples are not supported", .{}), .Packed => return sema.fail(block, src, "packed tuples are not supported", .{}), .Auto => {}, }; - const fields_len: u32 = @intCast(try sema.usizeCast(block, src, fields_val.sliceLen(mod))); - - // Because these three things each reference each other, `undefined` - // placeholders are used before being set after the struct type gains an - // InternPool index. - const new_decl_index = try sema.createAnonymousDeclTypeNamed(block, src, .{ - .ty = Type.noreturn, - .val = Value.@"unreachable", + .ty = Type.type, + .val = Value.fromInterned(wip_ty.index), }, name_strategy, "struct", inst); - const new_decl = mod.declPtr(new_decl_index); - new_decl.owns_tv = true; - errdefer { - new_decl.has_tv = false; // namespace and val were destroyed by later errdefers - mod.abortAnonDecl(new_decl_index); - } + mod.declPtr(new_decl_index).owns_tv = true; + errdefer mod.abortAnonDecl(new_decl_index); - const ty = try ip.getStructType(gpa, .{ - .decl = new_decl_index, - .namespace = .none, - .zir_index = .none, - .layout = layout, - .known_non_opv = false, - .fields_len = fields_len, - .requires_comptime = .unknown, - .is_tuple = is_tuple, - // So that we don't have to scan ahead, we allocate space in the struct - // type for alignments, comptime fields, and default inits. This might - // result in wasted space, however, this is a permitted encoding of - // struct types. - .any_comptime_fields = true, - .any_default_inits = true, - .inits_resolved = true, - .any_aligned_fields = true, - .captures = &.{}, - }); - // TODO: figure out InternPool removals for incremental compilation - //errdefer ip.remove(ty); - const struct_type = ip.loadStructType(ty); + const struct_type = ip.loadStructType(wip_ty.index); - new_decl.ty = Type.type; - new_decl.val = Value.fromInterned(ty); + for (0..fields_len) |field_idx| { + const field_info = try fields_val.elemValue(mod, field_idx); - // Fields - for (0..fields_len) |i| { - const elem_val = try fields_val.elemValue(mod, i); - const elem_struct_type = ip.loadStructType(ip.typeOf(elem_val.toIntern())); - const name_val = try elem_val.fieldValue(mod, elem_struct_type.nameIndex( - ip, - try ip.getOrPutString(gpa, "name"), - ).?); - const type_val = try elem_val.fieldValue(mod, elem_struct_type.nameIndex( - ip, - try ip.getOrPutString(gpa, "type"), - ).?); - const default_value_val = try elem_val.fieldValue(mod, elem_struct_type.nameIndex( - ip, - try ip.getOrPutString(gpa, "default_value"), - ).?); - const is_comptime_val = try elem_val.fieldValue(mod, elem_struct_type.nameIndex( - ip, - try ip.getOrPutString(gpa, "is_comptime"), - ).?); - const alignment_val = try elem_val.fieldValue(mod, elem_struct_type.nameIndex( - ip, - try ip.getOrPutString(gpa, "alignment"), - ).?); - - if (!try sema.intFitsInType(alignment_val, Type.u32, null)) { - return sema.fail(block, src, "alignment must fit in 'u32'", .{}); - } - const abi_align = (try alignment_val.getUnsignedIntAdvanced(mod, sema)).?; - - if (layout == .Packed) { - if (abi_align != 0) return sema.fail(block, src, "alignment in a packed struct field must be set to 0", .{}); - if (is_comptime_val.toBool()) return sema.fail(block, src, "packed struct fields cannot be marked comptime", .{}); - } else { - if (abi_align > 0 and !math.isPowerOfTwo(abi_align)) return sema.fail(block, src, "alignment value '{d}' is not a power of two or zero", .{abi_align}); - struct_type.field_aligns.get(ip)[i] = Alignment.fromByteUnits(abi_align); - } - if (layout == .Extern and is_comptime_val.toBool()) { - return sema.fail(block, src, "extern struct fields cannot be marked comptime", .{}); - } - - const field_name = try name_val.toIpString(Type.slice_const_u8, mod); + const field_name_val = try field_info.fieldValue(mod, 0); + const field_type_val = try field_info.fieldValue(mod, 1); + const field_default_value_val = try field_info.fieldValue(mod, 2); + const field_is_comptime_val = try field_info.fieldValue(mod, 3); + const field_alignment_val = try field_info.fieldValue(mod, 4); + const field_ty = field_type_val.toType(); + const field_name = try field_name_val.toIpString(Type.slice_const_u8, mod); if (is_tuple) { - const field_index = field_name.toUnsigned(ip) orelse return sema.fail( + const field_name_index = field_name.toUnsigned(ip) orelse return sema.fail( block, src, "tuple cannot have non-numeric field '{}'", .{field_name.fmt(ip)}, ); - - if (field_index >= fields_len) { + if (field_name_index != field_idx) { return sema.fail( block, src, - "tuple field {} exceeds tuple field count", - .{field_index}, + "tuple field name '{}' does not match field index {}", + .{ field_name_index, field_idx }, ); } } else if (struct_type.addFieldName(ip, field_name)) |prev_index| { @@ -21923,45 +21950,72 @@ fn reifyStruct( return sema.fail(block, src, "duplicate struct field name {}", .{field_name.fmt(ip)}); } - const field_ty = type_val.toType(); - const default_val = if (default_value_val.optionalValue(mod)) |opt_val| - (try sema.pointerDeref(block, src, opt_val, try mod.singleConstPtrType(field_ty)) orelse - return sema.failWithNeededComptime(block, src, .{ - .needed_comptime_reason = "struct field default value must be comptime-known", - })).toIntern() - else - .none; - if (is_comptime_val.toBool() and default_val == .none) { + if (any_aligned_fields) { + if (!try sema.intFitsInType(field_alignment_val, Type.u32, null)) { + return sema.fail(block, src, "alignment must fit in 'u32'", .{}); + } + + const byte_align = try field_alignment_val.toUnsignedIntAdvanced(sema); + if (byte_align == 0) { + if (layout != .Packed) { + struct_type.field_aligns.get(ip)[field_idx] = .none; + } + } else { + if (layout == .Packed) return sema.fail(block, src, "alignment in a packed struct field must be set to 0", .{}); + if (!math.isPowerOfTwo(byte_align)) return sema.fail(block, src, "alignment value '{d}' is not a power of two or zero", .{byte_align}); + struct_type.field_aligns.get(ip)[field_idx] = Alignment.fromNonzeroByteUnits(byte_align); + } + } + + const field_is_comptime = field_is_comptime_val.toBool(); + if (field_is_comptime) { + assert(any_comptime_fields); + switch (layout) { + .Extern => return sema.fail(block, src, "extern struct fields cannot be marked comptime", .{}), + .Packed => return sema.fail(block, src, "packed struct fields cannot be marked comptime", .{}), + .Auto => struct_type.setFieldComptime(ip, field_idx), + } + } + + const field_default: InternPool.Index = d: { + if (!any_default_inits) break :d .none; + const ptr_val = field_default_value_val.optionalValue(mod) orelse break :d .none; + const ptr_ty = try mod.singleConstPtrType(field_ty); + // Asserted comptime-dereferencable above. + const val = (try sema.pointerDeref(block, src, ptr_val, ptr_ty)).?; + // We already resolved this for deduplication, so we may as well do it now. + break :d (try sema.resolveLazyValue(val)).toIntern(); + }; + + if (field_is_comptime and field_default == .none) { return sema.fail(block, src, "comptime field without default initialization value", .{}); } - struct_type.field_types.get(ip)[i] = field_ty.toIntern(); - struct_type.field_inits.get(ip)[i] = default_val; - if (is_comptime_val.toBool()) - struct_type.setFieldComptime(ip, i); + struct_type.field_types.get(ip)[field_idx] = field_type_val.toIntern(); + if (field_default != .none) { + struct_type.field_inits.get(ip)[field_idx] = field_default; + } if (field_ty.zigTypeTag(mod) == .Opaque) { - const msg = msg: { + return sema.failWithOwnedErrorMsg(block, msg: { const msg = try sema.errMsg(block, src, "opaque types have unknown size and therefore cannot be directly embedded in structs", .{}); errdefer msg.destroy(gpa); try sema.addDeclaredHereNote(msg, field_ty); break :msg msg; - }; - return sema.failWithOwnedErrorMsg(block, msg); + }); } if (field_ty.zigTypeTag(mod) == .NoReturn) { - const msg = msg: { + return sema.failWithOwnedErrorMsg(block, msg: { const msg = try sema.errMsg(block, src, "struct fields cannot be 'noreturn'", .{}); errdefer msg.destroy(gpa); try sema.addDeclaredHereNote(msg, field_ty); break :msg msg; - }; - return sema.failWithOwnedErrorMsg(block, msg); + }); } if (layout == .Extern and !try sema.validateExternType(field_ty, .struct_field)) { - const msg = msg: { + return sema.failWithOwnedErrorMsg(block, msg: { const msg = try sema.errMsg(block, src, "extern structs cannot contain fields of type '{}'", .{field_ty.fmt(sema.mod)}); errdefer msg.destroy(gpa); @@ -21970,10 +22024,9 @@ fn reifyStruct( try sema.addDeclaredHereNote(msg, field_ty); break :msg msg; - }; - return sema.failWithOwnedErrorMsg(block, msg); + }); } else if (layout == .Packed and !try sema.validatePackedType(field_ty)) { - const msg = msg: { + return sema.failWithOwnedErrorMsg(block, msg: { const msg = try sema.errMsg(block, src, "packed structs cannot contain fields of type '{}'", .{field_ty.fmt(sema.mod)}); errdefer msg.destroy(gpa); @@ -21982,32 +22035,27 @@ fn reifyStruct( try sema.addDeclaredHereNote(msg, field_ty); break :msg msg; - }; - return sema.failWithOwnedErrorMsg(block, msg); + }); } } if (layout == .Packed) { - for (0..struct_type.field_types.len) |index| { - const field_ty = Type.fromInterned(struct_type.field_types.get(ip)[index]); + var fields_bit_sum: u64 = 0; + for (0..struct_type.field_types.len) |field_idx| { + const field_ty = Type.fromInterned(struct_type.field_types.get(ip)[field_idx]); sema.resolveTypeLayout(field_ty) catch |err| switch (err) { error.AnalysisFail => { const msg = sema.err orelse return err; - try sema.addFieldErrNote(Type.fromInterned(ty), index, msg, "while checking this field", .{}); + try sema.errNote(block, src, msg, "while checking a field of this struct", .{}); return err; }, else => return err, }; - } - - var fields_bit_sum: u64 = 0; - for (0..struct_type.field_types.len) |i| { - const field_ty = Type.fromInterned(struct_type.field_types.get(ip)[i]); fields_bit_sum += field_ty.bitSize(mod); } - if (backing_int_val.optionalValue(mod)) |backing_int_ty_val| { - const backing_int_ty = backing_int_ty_val.toType(); + if (opt_backing_int_val.optionalValue(mod)) |backing_int_val| { + const backing_int_ty = backing_int_val.toType(); try sema.checkBackingIntType(block, src, backing_int_ty, fields_bit_sum); struct_type.backingIntType(ip).* = backing_int_ty.toIntern(); } else { @@ -22016,9 +22064,8 @@ fn reifyStruct( } } - const decl_val = sema.analyzeDeclVal(block, src, new_decl_index); try mod.finalizeAnonDecl(new_decl_index); - return decl_val; + return Air.internedToRef(wip_ty.finish(ip, new_decl_index, .none)); } fn resolveVaListRef(sema: *Sema, block: *Block, src: LazySrcLoc, zir_ref: Zir.Inst.Ref) CompileError!Air.Inst.Ref { @@ -36963,8 +37010,8 @@ fn semaUnionFields(mod: *Module, arena: Allocator, union_type: InternPool.Loaded const gpa = mod.gpa; const ip = &mod.intern_pool; const decl_index = union_type.decl; - const zir = mod.namespacePtr(union_type.namespace).file_scope.zir; - const zir_index = union_type.zir_index.unwrap().?.resolve(ip); + const zir = mod.namespacePtr(union_type.namespace.unwrap().?).file_scope.zir; + const zir_index = union_type.zir_index.resolve(ip); const extended = zir.instructions.items(.data)[@intFromEnum(zir_index)].extended; assert(extended.opcode == .union_decl); const small: Zir.Inst.UnionDecl.Small = @bitCast(extended.small); @@ -37037,7 +37084,7 @@ fn semaUnionFields(mod: *Module, arena: Allocator, union_type: InternPool.Loaded .parent = null, .sema = &sema, .src_decl = decl_index, - .namespace = union_type.namespace, + .namespace = union_type.namespace.unwrap().?, .instructions = .{}, .inlining = null, .is_comptime = true, @@ -37357,7 +37404,7 @@ fn semaUnionFields(mod: *Module, arena: Allocator, union_type: InternPool.Loaded const enum_ty = try sema.generateUnionTagTypeNumbered(&block_scope, enum_field_names, enum_field_vals.keys(), mod.declPtr(union_type.decl)); union_type.tagTypePtr(ip).* = enum_ty; } else { - const enum_ty = try sema.generateUnionTagTypeSimple(&block_scope, enum_field_names, union_type.decl.toOptional()); + const enum_ty = try sema.generateUnionTagTypeSimple(&block_scope, enum_field_names, mod.declPtr(union_type.decl)); union_type.tagTypePtr(ip).* = enum_ty; } } @@ -37374,7 +37421,7 @@ fn generateUnionTagTypeNumbered( block: *Block, enum_field_names: []const InternPool.NullTerminatedString, enum_field_vals: []const InternPool.Index, - decl: *Module.Decl, + union_owner_decl: *Module.Decl, ) !InternPool.Index { const mod = sema.mod; const gpa = sema.gpa; @@ -37383,7 +37430,7 @@ fn generateUnionTagTypeNumbered( const src_decl = mod.declPtr(block.src_decl); const new_decl_index = try mod.allocateNewDecl(block.namespace, src_decl.src_node); errdefer mod.destroyDecl(new_decl_index); - const fqn = try decl.fullyQualifiedName(mod); + const fqn = try union_owner_decl.fullyQualifiedName(mod); const name = try ip.getOrPutStringFmt(gpa, "@typeInfo({}).Union.tag_type.?", .{fqn.fmt(ip)}); try mod.initNewAnonDecl(new_decl_index, src_decl.src_line, .{ .ty = Type.noreturn, @@ -37395,9 +37442,9 @@ fn generateUnionTagTypeNumbered( new_decl.owns_tv = true; new_decl.name_fully_qualified = true; - const enum_ty = try ip.getEnum(gpa, .{ + const enum_ty = try ip.getGeneratedTagEnumType(gpa, .{ .decl = new_decl_index, - .namespace = .none, + .owner_union_ty = union_owner_decl.val.toIntern(), .tag_ty = if (enum_field_vals.len == 0) (try mod.intType(.unsigned, 0)).toIntern() else @@ -37405,8 +37452,6 @@ fn generateUnionTagTypeNumbered( .names = enum_field_names, .values = enum_field_vals, .tag_mode = .explicit, - .zir_index = .none, - .captures = &.{}, }); new_decl.ty = Type.type; @@ -37420,20 +37465,14 @@ fn generateUnionTagTypeSimple( sema: *Sema, block: *Block, enum_field_names: []const InternPool.NullTerminatedString, - maybe_decl_index: InternPool.OptionalDeclIndex, + union_owner_decl: *Module.Decl, ) !InternPool.Index { const mod = sema.mod; const ip = &mod.intern_pool; const gpa = sema.gpa; const new_decl_index = new_decl_index: { - const decl_index = maybe_decl_index.unwrap() orelse { - break :new_decl_index try mod.createAnonymousDecl(block, .{ - .ty = Type.noreturn, - .val = Value.@"unreachable", - }); - }; - const fqn = try mod.declPtr(decl_index).fullyQualifiedName(mod); + const fqn = try union_owner_decl.fullyQualifiedName(mod); const src_decl = mod.declPtr(block.src_decl); const new_decl_index = try mod.allocateNewDecl(block.namespace, src_decl.src_node); errdefer mod.destroyDecl(new_decl_index); @@ -37447,9 +37486,9 @@ fn generateUnionTagTypeSimple( }; errdefer mod.abortAnonDecl(new_decl_index); - const enum_ty = try ip.getEnum(gpa, .{ + const enum_ty = try ip.getGeneratedTagEnumType(gpa, .{ .decl = new_decl_index, - .namespace = .none, + .owner_union_ty = union_owner_decl.val.toIntern(), .tag_ty = if (enum_field_names.len == 0) (try mod.intType(.unsigned, 0)).toIntern() else @@ -37457,8 +37496,6 @@ fn generateUnionTagTypeSimple( .names = enum_field_names, .values = &.{}, .tag_mode = .auto, - .zir_index = .none, - .captures = &.{}, }); const new_decl = mod.declPtr(new_decl_index); @@ -37643,6 +37680,8 @@ pub fn typeHasOnePossibleValue(sema: *Sema, ty: Type) CompileError!?Value { => unreachable, _ => switch (ip.items.items(.tag)[@intFromEnum(ty.toIntern())]) { + .removed => unreachable, + .type_int_signed, // i0 handled above .type_int_unsigned, // u0 handled above .type_pointer, diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index d056a384ee..d0f8f99037 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -3548,12 +3548,11 @@ pub const Object = struct { ); return ty; }, - .opaque_type => |opaque_type| { + .opaque_type => { const gop = try o.type_map.getOrPut(o.gpa, t.toIntern()); if (!gop.found_existing) { - const name = try o.builder.string(ip.stringToSlice( - try mod.opaqueFullyQualifiedName(opaque_type), - )); + const decl = mod.declPtr(ip.loadOpaqueType(t.toIntern()).decl); + const name = try o.builder.string(ip.stringToSlice(try decl.getFullyQualifiedName(mod))); gop.value_ptr.* = try o.builder.opaqueType(name); } return gop.value_ptr.*; diff --git a/src/type.zig b/src/type.zig index d19ad6f02e..8b2c6f2a1e 100644 --- a/src/type.zig +++ b/src/type.zig @@ -355,16 +355,16 @@ pub const Type = struct { try writer.writeAll("}"); }, - .union_type => |union_type| { - const decl = mod.declPtr(union_type.decl); + .union_type => { + const decl = mod.declPtr(ip.loadUnionType(ty.toIntern()).decl); try decl.renderFullyQualifiedName(mod, writer); }, - .opaque_type => |opaque_type| { - const decl = mod.declPtr(opaque_type.decl); + .opaque_type => { + const decl = mod.declPtr(ip.loadOpaqueType(ty.toIntern()).decl); try decl.renderFullyQualifiedName(mod, writer); }, - .enum_type => |enum_type| { - const decl = mod.declPtr(enum_type.decl); + .enum_type => { + const decl = mod.declPtr(ip.loadEnumType(ty.toIntern()).decl); try decl.renderFullyQualifiedName(mod, writer); }, .func_type => |fn_info| { @@ -2845,9 +2845,9 @@ pub const Type = struct { pub fn getNamespaceIndex(ty: Type, mod: *Module) InternPool.OptionalNamespaceIndex { const ip = &mod.intern_pool; return switch (ip.indexToKey(ty.toIntern())) { - .opaque_type => ip.loadOpaqueType(ty.toIntern()).namespace.toOptional(), + .opaque_type => ip.loadOpaqueType(ty.toIntern()).namespace, .struct_type => ip.loadStructType(ty.toIntern()).namespace, - .union_type => ip.loadUnionType(ty.toIntern()).namespace.toOptional(), + .union_type => ip.loadUnionType(ty.toIntern()).namespace, .enum_type => ip.loadEnumType(ty.toIntern()).namespace, else => .none, @@ -3180,17 +3180,8 @@ pub const Type = struct { } pub fn declSrcLocOrNull(ty: Type, mod: *Module) ?Module.SrcLoc { - return switch (mod.intern_pool.indexToKey(ty.toIntern())) { - .struct_type => |struct_type| { - return mod.declPtr(struct_type.decl.unwrap() orelse return null).srcLoc(mod); - }, - .union_type => |union_type| { - return mod.declPtr(union_type.decl).srcLoc(mod); - }, - .opaque_type => |opaque_type| mod.opaqueSrcLoc(opaque_type), - .enum_type => |enum_type| mod.declPtr(enum_type.decl).srcLoc(mod), - else => null, - }; + const decl = ty.getOwnerDeclOrNull(mod) orelse return null; + return mod.declPtr(decl).srcLoc(mod); } pub fn getOwnerDecl(ty: Type, mod: *Module) InternPool.DeclIndex { @@ -3198,11 +3189,12 @@ pub const Type = struct { } pub fn getOwnerDeclOrNull(ty: Type, mod: *Module) ?InternPool.DeclIndex { - return switch (mod.intern_pool.indexToKey(ty.toIntern())) { - .struct_type => |struct_type| struct_type.decl.unwrap(), - .union_type => |union_type| union_type.decl, - .opaque_type => |opaque_type| opaque_type.decl, - .enum_type => |enum_type| enum_type.decl, + const ip = &mod.intern_pool; + return switch (ip.indexToKey(ty.toIntern())) { + .struct_type => ip.loadStructType(ty.toIntern()).decl.unwrap(), + .union_type => ip.loadUnionType(ty.toIntern()).decl, + .opaque_type => ip.loadOpaqueType(ty.toIntern()).decl, + .enum_type => ip.loadEnumType(ty.toIntern()).decl, else => null, }; } @@ -3287,9 +3279,9 @@ pub const Type = struct { const ip = &zcu.intern_pool; return switch (ip.indexToKey(ty.toIntern())) { .struct_type => ip.loadStructType(ty.toIntern()).zir_index.unwrap(), - .union_type => ip.loadUnionType(ty.toIntern()).zir_index.unwrap(), + .union_type => ip.loadUnionType(ty.toIntern()).zir_index, .enum_type => ip.loadEnumType(ty.toIntern()).zir_index.unwrap(), - .opaque_type => ip.loadOpaqueType(ty.toIntern()).zir_index.unwrap(), + .opaque_type => ip.loadOpaqueType(ty.toIntern()).zir_index, else => null, }; }