From 187fef209f73336163337474dc02f46c7c89ac3a Mon Sep 17 00:00:00 2001 From: Matthew Lugg Date: Thu, 29 Jan 2026 08:54:59 +0000 Subject: [PATCH] compiler: rework OPV and noreturn-like types --- src/Air/Liveness.zig | 2 +- src/Air/Liveness/Verify.zig | 2 +- src/InternPool.zig | 169 ++---- src/Sema.zig | 438 +++++++--------- src/Sema/bitcast.zig | 2 - src/Sema/type_resolution.zig | 122 +++-- src/Type.zig | 917 ++++++++++++++++----------------- src/Value.zig | 7 +- src/Zcu/PerThread.zig | 9 +- src/codegen.zig | 2 - src/codegen/aarch64/Select.zig | 8 +- src/codegen/c.zig | 28 +- src/codegen/c/Type.zig | 14 +- src/codegen/llvm.zig | 3 - src/codegen/spirv/CodeGen.zig | 6 +- src/codegen/wasm/CodeGen.zig | 12 +- src/codegen/x86_64/CodeGen.zig | 2 +- src/link/Dwarf.zig | 41 +- src/print_value.zig | 2 - 19 files changed, 827 insertions(+), 959 deletions(-) diff --git a/src/Air/Liveness.zig b/src/Air/Liveness.zig index a85944c4678455126d04403e1c48486db9d6e860..5c98dc96fca8c54208556eff486475cede9513a6 100644 --- a/src/Air/Liveness.zig +++ b/src/Air/Liveness.zig @@ -999,7 +999,7 @@ fn analyzeInstBlock( // If the block is noreturn, block deaths not only aren't useful, they're impossible to // find: there could be more stuff alive after the block than before it! - if (!a.intern_pool.isNoReturn(ty.toIntern())) { + if (!ty.isNoReturn(a.zcu)) { // The block kills the difference in the live sets const block_scope = data.block_scopes.get(inst).?; const num_deaths = data.live_set.count() - block_scope.live_set.count(); diff --git a/src/Air/Liveness/Verify.zig b/src/Air/Liveness/Verify.zig index fc83574d070c018fc54ed095d4c67f0bf7698fb2..7f820e65981b1d4247b363c061269c8d893ce239 100644 --- a/src/Air/Liveness/Verify.zig +++ b/src/Air/Liveness/Verify.zig @@ -465,7 +465,7 @@ fn verifyBody(self: *Verify, body: []const Air.Inst.Index) Error!void { for (block_liveness.deaths) |death| try self.verifyDeath(inst, death); - if (ip.isNoReturn(block_ty.toIntern())) { + if (block_ty.isNoReturn(self.zcu)) { assert(!self.blocks.contains(inst)); } else { var live = if (self.blocks.fetchRemove(inst)) |kv| kv.value else { diff --git a/src/InternPool.zig b/src/InternPool.zig index 7028d690094d195183e3e33b991ddc2ce17572b1..31e37dd98e80f38e1141ce70d86940511cfe1914 100644 --- a/src/InternPool.zig +++ b/src/InternPool.zig @@ -17,6 +17,7 @@ const Hash = std.hash.Wyhash; const Zir = std.zig.Zir; const Zcu = @import("Zcu.zig"); +const TypeClass = @import("Type.zig").Class; /// One item per thread, indexed by `tid`, which is dense and unique per thread. locals: []Local, @@ -2113,10 +2114,6 @@ pub const Key = union(enum) { enum_literal: NullTerminatedString, /// A specific enum tag, indicated by the integer tag value. enum_tag: EnumTag, - /// An empty enum or union. TODO: this value's existence is strange, because such a type in - /// reality has no values. See #15909. - /// Payload is the type for which we are an empty value. - empty_enum_value: Index, float: Float, ptr: Ptr, slice: Slice, @@ -2722,7 +2719,6 @@ pub const Key = union(enum) { .err, .enum_literal, .enum_tag, - .empty_enum_value, .inferred_error_set_type, .un, => |x| Hash.hash(seed, asBytes(&x)), @@ -3005,10 +3001,6 @@ pub const Key = union(enum) { const b_info = b.enum_tag; return std.meta.eql(a_info, b_info); }, - .empty_enum_value => |a_info| { - const b_info = b.empty_enum_value; - return a_info == b_info; - }, .bitpack => |a_info| { const b_info = b.bitpack; return a_info.ty == b_info.ty and a_info.backing_int_val == b_info.backing_int_val; @@ -3294,10 +3286,8 @@ pub const Key = union(enum) { .enum_literal => .enum_literal_type, .undef => |x| x, - .empty_enum_value => |x| x, .simple_value => |s| switch (s) { - .undefined => .undefined_type, .void => .void_type, .null => .null_type, .false, .true => .bool_type, @@ -3356,10 +3346,7 @@ pub const LoadedStructType = struct { field_runtime_order: RuntimeOrder.Slice, field_offsets: Offsets, packed_backing_int_type: Index, - has_no_possible_value: bool, - has_one_possible_value: bool, - comptime_only: bool, - has_runtime_bits: bool, + class: TypeClass, size: u32, alignment: Alignment, @@ -3535,19 +3522,21 @@ pub const LoadedUnionType = struct { // The remaining fields are only valid once the union's layout is resolved. field_types: Index.Slice, field_aligns: Alignment.Slice, - runtime_tag: RuntimeTag, - /// Even if `runtime_tag == .none`, this is populated with the union's "hypothetical" tag type. + tag_usage: TagUsage, + /// While `tag_usage` indicates whether the union should logically contain a tag, it may be + /// omitted if the union layout is resolved as OPV or NPV. This field is `true` iff there is an + /// actual runtime tag in the union layout. + has_runtime_tag: bool, + /// Even if `tag_usage == .none` and `has_runtime_tag == false`, this is still populated with + /// the union's "hypothetical" tag type. enum_tag_type: Index, packed_backing_int_type: Index, - has_no_possible_value: bool, - has_one_possible_value: bool, - comptime_only: bool, - has_runtime_bits: bool, + class: TypeClass, size: u32, padding: u32, alignment: Alignment, - pub const RuntimeTag = enum(u2) { + pub const TagUsage = enum(u2) { none, safety, tagged, @@ -3733,10 +3722,7 @@ pub fn loadStructType(ip: *const InternPool, index: Index) LoadedStructType { .field_runtime_order = field_runtime_order, .field_offsets = field_offsets, .packed_backing_int_type = .none, - .has_no_possible_value = extra.data.flags.has_no_possible_value, - .has_one_possible_value = extra.data.flags.has_one_possible_value, - .comptime_only = extra.data.flags.comptime_only, - .has_runtime_bits = extra.data.flags.has_runtime_bits, + .class = extra.data.flags.class, .size = extra.data.size, .alignment = extra.data.flags.alignment, }; @@ -3797,10 +3783,7 @@ pub fn loadStructType(ip: *const InternPool, index: Index) LoadedStructType { .field_runtime_order = .empty, .field_offsets = .empty, .packed_backing_int_type = extra.data.backing_int_type, - .has_no_possible_value = undefined, - .has_one_possible_value = undefined, - .comptime_only = undefined, - .has_runtime_bits = undefined, + .class = undefined, .size = undefined, .alignment = undefined, }; @@ -3865,7 +3848,7 @@ pub fn loadUnionType(ip: *const InternPool, index: Index) LoadedUnionType { .auto => .auto, .@"extern" => .@"extern", }, - .runtime_tag = extra.data.flags.runtime_tag, + .tag_usage = extra.data.flags.tag_usage, .enum_tag_mode = extra.data.flags.enum_tag_mode, .enum_tag_type = extra.data.enum_tag_type, .packed_backing_mode = undefined, @@ -3874,10 +3857,8 @@ pub fn loadUnionType(ip: *const InternPool, index: Index) LoadedUnionType { .want_layout = extra.data.flags.want_layout, .field_types = field_types, .field_aligns = field_aligns, - .has_no_possible_value = extra.data.flags.has_no_possible_value, - .has_one_possible_value = extra.data.flags.has_one_possible_value, - .comptime_only = extra.data.flags.comptime_only, - .has_runtime_bits = extra.data.flags.has_runtime_bits, + .has_runtime_tag = extra.data.flags.has_runtime_tag, + .class = extra.data.flags.class, .size = extra.data.size, .padding = extra.data.padding, .alignment = extra.data.flags.alignment, @@ -3919,7 +3900,7 @@ pub fn loadUnionType(ip: *const InternPool, index: Index) LoadedUnionType { .name_nav = extra.data.name_nav, .namespace = extra.data.namespace, .layout = .@"packed", - .runtime_tag = .none, + .tag_usage = .none, .enum_tag_mode = .auto, .enum_tag_type = extra.data.enum_tag_type, .packed_backing_mode = backing_mode, @@ -3928,10 +3909,8 @@ pub fn loadUnionType(ip: *const InternPool, index: Index) LoadedUnionType { .want_layout = extra.data.bits.want_layout, .field_types = field_types, .field_aligns = .empty, - .has_no_possible_value = undefined, - .has_one_possible_value = undefined, - .comptime_only = undefined, - .has_runtime_bits = undefined, + .has_runtime_tag = undefined, + .class = undefined, .size = undefined, .padding = undefined, .alignment = undefined, @@ -4818,7 +4797,7 @@ pub const static_keys: [static_len]Key = .{ .values = .empty, } }, - .{ .simple_value = .undefined }, + .{ .undef = .undefined_type }, .{ .undef = .bool_type }, .{ .undef = .usize_type }, .{ .undef = .u1_type }, @@ -5682,23 +5661,14 @@ pub const Tag = enum(u8) { any_field_defaults: bool, any_field_aligns: bool, - /// Whether the struct is an OPV type. Always `false` until layout resolved. - /// The actual OPV is not cached, but caching this bit of state means we avoid - /// repeatedly doing redundant checks to find that the struct is not OPV! - has_one_possible_value: bool, - /// Like `has_one_possible_value`, but for a "noreturn" union (where all fields are noreturn). - has_no_possible_value: bool, - /// Whether the struct is comptime-only. Always `false` until layout resolved. - comptime_only: bool, - /// Whether the struct has runtime bits. Always `false` until layout resolved. - has_runtime_bits: bool, + class: TypeClass, /// Alignment of the whole struct. Always `.none` until layout resolved. alignment: Alignment, want_layout: bool, want_defaults: bool, - _: u14 = 0, + _: u15 = 0, }; }; @@ -5778,18 +5748,11 @@ pub const Tag = enum(u8) { layout: enum(u1) { auto, @"extern" }, any_field_aligns: bool, - runtime_tag: LoadedUnionType.RuntimeTag, + tag_usage: LoadedUnionType.TagUsage, + + class: TypeClass, + has_runtime_tag: bool, - /// Whether the union is an OPV type. Always `false` until layout resolved. - /// The actual OPV is not cached, but caching this bit of state means we avoid - /// repeatedly doing redundant checks to find that the union is not OPV! - has_one_possible_value: bool, - /// Like `has_one_possible_value`, but for a "noreturn" union (where all fields are noreturn). - has_no_possible_value: bool, - /// Whether the union is comptime-only. Always `false` until layout resolved. - comptime_only: bool, - /// Whether the union has runtime bits. Always `false` until layout resolved. - has_runtime_bits: bool, /// Alignment of the whole union. Always `.none` until layout resolved. alignment: Alignment, @@ -5970,8 +5933,6 @@ pub const SimpleType = enum(u32) { }; pub const SimpleValue = enum(u32) { - /// This is untyped `undefined`. - undefined = @intFromEnum(Index.undef), void = @intFromEnum(Index.void_value), /// This is untyped `null`. null = @intFromEnum(Index.null_value), @@ -7016,11 +6977,6 @@ pub fn indexToKey(ip: *const InternPool, index: Index) Key { } }; }, - .type_enum_auto, - .type_enum_explicit, - .type_union, - => .{ .empty_enum_value = ty }, - else => unreachable, }; }, @@ -7914,11 +7870,6 @@ pub fn get(ip: *InternPool, gpa: Allocator, io: Io, tid: Zcu.PerThread.Id, key: }); }, - .empty_enum_value => |enum_or_union_ty| items.appendAssumeCapacity(.{ - .tag = .only_possible_value, - .data = @intFromEnum(enum_or_union_ty), - }), - .float => |float| { switch (float.ty) { .f16_type => items.appendAssumeCapacity(.{ @@ -8302,10 +8253,7 @@ pub fn getDeclaredStructType( .any_comptime_fields = ini.any_comptime_fields, .any_field_defaults = ini.any_field_defaults, .any_field_aligns = ini.any_field_aligns, - .has_one_possible_value = false, - .has_no_possible_value = false, - .comptime_only = false, - .has_runtime_bits = false, + .class = .no_possible_value, .alignment = .none, .want_layout = false, .want_defaults = false, @@ -8456,10 +8404,7 @@ pub fn getReifiedStructType(ip: *InternPool, gpa: Allocator, io: Io, tid: Zcu.Pe .any_comptime_fields = ini.any_comptime_fields, .any_field_defaults = ini.any_field_defaults, .any_field_aligns = ini.any_field_aligns, - .has_one_possible_value = false, - .has_no_possible_value = false, - .comptime_only = false, - .has_runtime_bits = false, + .class = .no_possible_value, .alignment = .none, .want_layout = false, .want_defaults = false, @@ -8535,7 +8480,7 @@ pub fn getDeclaredUnionType( fields_len: u32, layout: std.builtin.Type.ContainerLayout, any_field_aligns: bool, - runtime_tag: LoadedUnionType.RuntimeTag, + tag_usage: LoadedUnionType.TagUsage, enum_tag_mode: BackingTypeMode, packed_backing_mode: BackingTypeMode, }, @@ -8617,11 +8562,9 @@ pub fn getDeclaredUnionType( .enum_tag_mode = ini.enum_tag_mode, .layout = if (is_extern) .@"extern" else .auto, .any_field_aligns = ini.any_field_aligns, - .runtime_tag = ini.runtime_tag, - .has_one_possible_value = false, - .has_no_possible_value = false, - .comptime_only = false, - .has_runtime_bits = false, + .tag_usage = ini.tag_usage, + .class = .no_possible_value, + .has_runtime_tag = false, .alignment = .none, .want_layout = false, }, @@ -8658,8 +8601,8 @@ pub fn getReifiedUnionType(ip: *InternPool, gpa: Allocator, io: Io, tid: Zcu.Per fields_len: u32, layout: std.builtin.Type.ContainerLayout, any_field_aligns: bool, - runtime_tag: LoadedUnionType.RuntimeTag, - /// Explicitly specified enum tag type. `.none` if `runtime_tag != .tagged`. + tag_usage: LoadedUnionType.TagUsage, + /// Explicitly specified enum tag type. `.none` if `tag_usage != .tagged`. enum_tag_type: Index, /// Explicitly specified backing int type. `.none` if not packed or if backing type is inferred. packed_backing_int_type: Index, @@ -8745,11 +8688,9 @@ pub fn getReifiedUnionType(ip: *InternPool, gpa: Allocator, io: Io, tid: Zcu.Per .enum_tag_mode = if (ini.enum_tag_type == .none) .auto else .explicit, .layout = if (is_extern) .@"extern" else .auto, .any_field_aligns = ini.any_field_aligns, - .runtime_tag = ini.runtime_tag, - .has_one_possible_value = false, - .has_no_possible_value = false, - .comptime_only = false, - .has_runtime_bits = false, + .tag_usage = ini.tag_usage, + .class = .no_possible_value, + .has_runtime_tag = false, .alignment = .none, .want_layout = false, }, @@ -12007,20 +11948,6 @@ pub fn funcTypeReturnType(ip: *const InternPool, ty: Index) Index { ]); } -pub fn isNoReturn(ip: *const InternPool, ty: Index) bool { - switch (ty) { - .noreturn_type => return true, - else => { - const unwrapped_ty = ty.unwrap(ip); - const ty_item = unwrapped_ty.getItem(ip); - return switch (ty_item.tag) { - .type_error_set => unwrapped_ty.getExtra(ip).view().items(.@"0")[ty_item.data + std.meta.fieldIndex(Tag.ErrorSet, "names_len").?] == 0, - else => false, - }; - }, - } -} - pub fn isUndef(ip: *const InternPool, val: Index) bool { return val == .undef or val.unwrap(ip).getTag(ip) == .undef; } @@ -12823,10 +12750,7 @@ pub fn resolveStructLayout( struct_type: Index, size: u32, alignment: Alignment, - has_no_possible_value: bool, - has_one_possible_value: bool, - comptime_only: bool, - has_runtime_bits: bool, + class: TypeClass, ) void { const unwrapped_index = struct_type.unwrap(ip); @@ -12840,10 +12764,7 @@ pub fn resolveStructLayout( extra_items[item.data + std.meta.fieldIndex(Tag.TypeStruct, "size").?] = size; const flags: *Tag.TypeStruct.Flags = @ptrCast(&extra_items[item.data + std.meta.fieldIndex(Tag.TypeStruct, "flags").?]); - flags.has_no_possible_value = has_no_possible_value; - flags.has_one_possible_value = has_one_possible_value; - flags.comptime_only = comptime_only; - flags.has_runtime_bits = has_runtime_bits; + flags.class = class; flags.alignment = alignment; } @@ -12856,13 +12777,11 @@ pub fn resolveUnionLayout( io: Io, union_type: Index, enum_tag_type: Index, + class: TypeClass, + has_runtime_tag: bool, size: u32, padding: u32, alignment: Alignment, - has_no_possible_value: bool, - has_one_possible_value: bool, - comptime_only: bool, - has_runtime_bits: bool, ) void { const unwrapped_index = union_type.unwrap(ip); @@ -12878,10 +12797,8 @@ pub fn resolveUnionLayout( extra_items[item.data + std.meta.fieldIndex(Tag.TypeUnion, "size").?] = size; extra_items[item.data + std.meta.fieldIndex(Tag.TypeUnion, "padding").?] = padding; const flags: *Tag.TypeUnion.Flags = @ptrCast(&extra_items[item.data + std.meta.fieldIndex(Tag.TypeUnion, "flags").?]); - flags.has_no_possible_value = has_no_possible_value; - flags.has_one_possible_value = has_one_possible_value; - flags.comptime_only = comptime_only; - flags.has_runtime_bits = has_runtime_bits; + flags.class = class; + flags.has_runtime_tag = has_runtime_tag; flags.alignment = alignment; } diff --git a/src/Sema.zig b/src/Sema.zig index 60f04974372927d734327b71cef6118d53bc0797..cd6625fdaba233ed72cdbd31b9d79617dd40267d 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -178,8 +178,11 @@ const ComptimeAlloc = struct { fn newComptimeAlloc(sema: *Sema, block: *Block, src: LazySrcLoc, ty: Type, alignment: Alignment) !ComptimeAllocIndex { const pt = sema.pt; - // Explicit guard because this call mutates the InternPool so cannot be optimized out. - if (std.debug.runtime_safety) assert(ty.onePossibleValue(pt) catch @panic("") == null); + switch (ty.classify(pt.zcu)) { + .no_possible_value => unreachable, + .one_possible_value => unreachable, + else => {}, + } const idx = sema.comptime_allocs.items.len; try sema.comptime_allocs.append(sema.gpa, .{ @@ -1991,31 +1994,28 @@ fn analyzeBodyInner( break :blk .void_value; }, }; - if (sema.isNoReturn(air_ref)) { - // We're going to assume that the body itself is noreturn, so let's ensure that now - assert(block.instructions.items.len > 0); - assert(sema.isNoReturn(block.instructions.items[block.instructions.items.len - 1].toRef())); - break; - } + const is_inferred_alloc = if (air_ref.toIndex()) |air_inst| switch (sema.air_instructions.items(.tag)[@intFromEnum(air_inst)]) { + .inferred_alloc, .inferred_alloc_comptime => true, + else => false, + } else false; // We must resolve the layout of a type before creating a value of that type. Therefore, - // the layout of the type of `air_ref` must already be resolved. - check_type: { - if (air_ref.toIndex()) |air_inst| switch (sema.air_instructions.items(.tag)[@intFromEnum(air_inst)]) { - .inferred_alloc, .inferred_alloc_comptime => break :check_type, - else => {}, - }; - sema.typeOf(air_ref).assertHasLayout(zcu); - // If the type has an OPV, `air_ref` must be that OPV: there is no other interned value - // it could be, and it would be a bug for the value to not be comptime-known when it has - // an OPV. Behind a `std.debug.runtime_safety` check because `onePossibleValue` mutates - // the InternPool so cannot be optimized out. - if (std.debug.runtime_safety) { - if (try sema.typeOf(air_ref).onePossibleValue(pt)) |opv| { - assert(air_ref == Air.Inst.Ref.fromValue(opv)); - } - } - } + // the layout of the type of `air_ref` must already be resolved. The call to `classify` + // doubles as an assertion of this. + if (!is_inferred_alloc) switch (sema.typeOf(air_ref).classify(zcu)) { + .no_possible_value => { + // The instruction result was noreturn, which should mean that the body itself now + // ends with a noreturn instruction. Let's confirm that. + const last_inst = block.instructions.items[block.instructions.items.len - 1]; + const last_inst_ty = sema.typeOf(last_inst.toRef()); + assert(last_inst_ty.classify(zcu) == .no_possible_value); + break; + }, + .one_possible_value => assert(air_ref.toInterned() != null), // the value should be comptime-known + .partially_comptime => assert(air_ref.toInterned() != null), // the value should be comptime-known + .fully_comptime => assert(air_ref.toInterned() != null), // the value should be comptime-known + .runtime => {}, + }; map.putAssumeCapacity(inst, air_ref); i += 1; @@ -2287,11 +2287,12 @@ fn resolveValue(sema: *Sema, inst: Air.Inst.Ref) ?Value { .inferred_alloc_comptime => unreachable, // assertion failure else => {}, } - // Assert that the type is not OPV -- if it was, the value would have been comptime-known. - // Explicit guard because this could add to the InternPool so cannot be optimized away. - if (std.debug.runtime_safety) { - const opv = sema.typeOf(inst).onePossibleValue(sema.pt) catch @panic("oom in assert"); - assert(opv == null); + switch (sema.typeOf(inst).classify(zcu)) { + .no_possible_value => unreachable, // values of this type do not exist + .one_possible_value => unreachable, // the value should be comptime-known + .partially_comptime => unreachable, // the value should be comptime-known + .fully_comptime => unreachable, // the value should be comptime-known + .runtime => {}, } return null; } @@ -4645,16 +4646,21 @@ fn zirValidateDeref(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileErr const elem_ty = operand_ty.childType(zcu); try sema.ensureLayoutResolved(elem_ty, src); - if (try elem_ty.onePossibleValue(pt) != null) { - // No need to validate the actual pointer value, we don't need it! - return; - } + const need_comptime = switch (elem_ty.classify(zcu)) { + .no_possible_value => return sema.fail(block, src, "cannot load {s} type '{f}'", .{ + if (elem_ty.zigTypeTag(zcu) == .@"opaque") "opaque" else "uninstantiable", + elem_ty.fmt(pt), + }), + .one_possible_value => return, // no need to validate the actual pointer value! + .runtime => false, + .partially_comptime, .fully_comptime => true, + }; if (sema.resolveValue(operand)) |val| { if (val.isUndef(zcu)) { return sema.fail(block, src, "cannot dereference undefined value", .{}); } - } else if (elem_ty.comptimeOnly(zcu)) { + } else if (need_comptime) { const msg = msg: { const msg = try sema.errMsg( src, @@ -4870,7 +4876,7 @@ fn storeToInferredAllocComptime( .is_const = iac.is_const, }, }); - if (try operand_ty.onePossibleValue(pt) != null or + if (operand_ty.classify(zcu) == .one_possible_value or (iac.is_const and !operand_val.canMutateComptimeVarState(zcu))) { iac.ptr = try pt.intern(.{ .ptr = .{ @@ -6672,7 +6678,7 @@ const CallArgsInfo = union(enum) { return sema.failWithNeededComptime(block, cai.argSrc(block, arg_index), null); } - if (sema.typeOf(uncoerced_arg).zigTypeTag(zcu) == .noreturn) { + if (sema.typeOf(uncoerced_arg).classify(zcu) == .no_possible_value) { // This terminates resolution of arguments. The caller should // propagate this. return uncoerced_arg; @@ -6928,7 +6934,7 @@ fn analyzeCall( arg.* = try args_info.analyzeArg(sema, block, arg_idx, param_ty, func_ty_info, callee, maybe_func_inst); const arg_ty = sema.typeOf(arg.*); - if (arg_ty.zigTypeTag(zcu) == .noreturn) { + if (arg_ty.classify(zcu) == .no_possible_value) { return arg.*; // terminate analysis here } @@ -7183,7 +7189,7 @@ fn analyzeCall( return sema.handleTailCall(block, call_src, runtime_func_ty, maybe_opv); } - if (ip.isNoReturn(resolved_ret_ty.toIntern())) { + if (resolved_ret_ty.isNoReturn(zcu)) { const want_check = c: { if (!block.wantSafety()) break :c false; if (func_val != null) break :c false; @@ -7989,16 +7995,16 @@ fn zirIntFromEnum(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError const enum_tag: Air.Inst.Ref = switch (operand_ty.zigTypeTag(zcu)) { .@"enum" => operand, .@"union" => blk: { - const tag_ty = operand_ty.unionTagType(zcu) orelse { + if (operand_ty.unionTagType(zcu) == null) { return sema.fail( block, operand_src, "untagged union '{f}' cannot be converted to integer", .{operand_ty.fmt(pt)}, ); - }; + } - break :blk try sema.unionToTag(block, tag_ty, operand, operand_src); + break :blk try sema.unionToTag(block, operand); }, else => { return sema.fail(block, operand_src, "expected enum or tagged union, found '{f}'", .{ @@ -10151,9 +10157,8 @@ fn analyzeSwitchBlock( const maybe_operand_opv = try operand_ty.onePossibleValue(pt); const init_cond: Air.Inst.Ref, const item_ty: Type = switch (operand_ty.zigTypeTag(zcu)) { .@"union" => tag: { - const tag_ty = operand_ty.unionTagType(zcu).?; - const tag_val = try sema.unionToTag(block, tag_ty, val, operand_src); - break :tag .{ tag_val, tag_ty }; + const tag_val = try sema.unionToTag(block, val); + break :tag .{ tag_val, sema.typeOf(tag_val) }; }, else => .{ if (maybe_operand_opv) |operand_opv| .fromValue(operand_opv) else val, @@ -10245,7 +10250,7 @@ fn analyzeSwitchBlock( .{ new_operand, .none }; const new_cond_ref = if (union_originally) - try sema.unionToTag(child_block, item_ty, new_val, src) + try sema.unionToTag(child_block, new_val) else new_val; @@ -12147,8 +12152,7 @@ fn analyzeSwitchTagCapture( .item_refs => |refs| if (refs.len == 1) return refs[0], .special => {}, } - const tag_ty = operand_ty.unionTagType(zcu).?; - return sema.unionToTag(case_block, tag_ty, operand_val, tag_capture_src); + return sema.unionToTag(case_block, operand_val); } fn analyzeSwitchPayloadCapture( @@ -15389,9 +15393,12 @@ fn analyzePtrArithmetic( const elem_ty: Type = .fromInterned(ptr_info.child); elem_ty.assertHasLayout(zcu); - if (elem_ty.abiSize(zcu) == 0) { - // Offset will be multiplied by zero, so result is the same as the base pointer. - return ptr; + switch (elem_ty.classify(zcu)) { + .no_possible_value, .one_possible_value => { + // Offset will be multiplied by zero, so result is the same as the base pointer. + return ptr; + }, + else => {}, } const new_ptr_ty = t: { @@ -15757,7 +15764,7 @@ fn analyzeCmpUnionTag( if (sema.resolveValue(coerced_tag)) |enum_val| { if (enum_val.isUndef(zcu)) return .undef_bool; const field_ty = union_ty.unionFieldType(enum_val, zcu).?; - if (field_ty.zigTypeTag(zcu) == .noreturn) { + if (field_ty.classify(zcu) == .no_possible_value) { return .bool_false; } } @@ -15934,39 +15941,22 @@ fn zirSizeOf(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air. const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].un_node; const operand_src = block.builtinCallArgSrc(inst_data.src_node, 0); const ty = try sema.resolveType(block, operand_src, inst_data.operand); - switch (ty.zigTypeTag(zcu)) { - .@"fn", - .noreturn, - .undefined, - .null, - .@"opaque", - => return sema.fail(block, operand_src, "no size available for type '{f}'", .{ty.fmt(pt)}), - - .type, - .enum_literal, - .comptime_float, - .comptime_int, - .void, - => return .zero, - - .bool, - .int, - .float, - .pointer, - .array, - .@"struct", - .optional, - .error_union, - .error_set, - .@"enum", - .@"union", - .vector, - .frame, - .@"anyframe", - => {}, - } try sema.ensureLayoutResolved(ty, operand_src); - return .fromValue(try pt.intValue(.comptime_int, ty.abiSize(zcu))); + switch (ty.classify(zcu)) { + .no_possible_value, + => return sema.fail(block, operand_src, "no size available for uninstantiable type '{f}'", .{ty.fmt(pt)}), + + .partially_comptime, + .fully_comptime, + => return sema.fail(block, operand_src, "no size available for comptime-only type '{f}'", .{ty.fmt(pt)}), + + .one_possible_value => { + assert(ty.abiSize(zcu) == 0); + return .zero; + }, + + .runtime => return .fromValue(try pt.intValue(.comptime_int, ty.abiSize(zcu))), + } } fn zirBitSizeOf(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { @@ -18631,9 +18621,9 @@ fn zirStructInit( const tag_val = try pt.enumValueFieldIndex(tag_ty, field_index); const field_ty: Type = .fromInterned(zcu.typeToUnion(resolved_ty).?.field_types.get(ip)[field_index]); - if (field_ty.zigTypeTag(zcu) == .noreturn) { + if (field_ty.classify(zcu) == .no_possible_value) { return sema.failWithOwnedErrorMsg(block, msg: { - const msg = try sema.errMsg(src, "cannot initialize 'noreturn' field of union", .{}); + const msg = try sema.errMsg(src, "cannot initialize union field with uninstantiable type '{f}'", .{field_ty.fmt(pt)}); errdefer msg.destroy(sema.gpa); try sema.addFieldErrNote(resolved_ty, field_index, msg, "field '{f}' declared here", .{ @@ -18648,7 +18638,13 @@ fn zirStructInit( const init_inst = try sema.coerce(block, field_ty, uncoerced_init_inst, field_src); if (resolved_ty.containerLayout(zcu) == .@"packed") { - return sema.bitCast(block, resolved_ty, init_inst, src, field_src); + const union_val = try sema.bitCast(block, resolved_ty, init_inst, src, field_src); + const result_val = try sema.coerce(block, result_ty, union_val, src); + if (is_ref) { + return sema.analyzeRef(block, src, result_val); + } else { + return result_val; + } } if (sema.resolveValue(init_inst)) |val| { @@ -18681,9 +18677,6 @@ fn zirStructInit( const base_ptr = try sema.optEuBasePtrInit(block, alloc, src); const field_ptr = try sema.unionFieldPtr(block, field_src, base_ptr, field_name, field_src, resolved_ty, true); try sema.storePtr(block, src, field_ptr, init_inst); - if (try tag_ty.onePossibleValue(pt) == null) { - _ = try block.addBinOp(.set_union_tag, base_ptr, .fromValue(tag_val)); - } return sema.makePtrConst(block, alloc); } @@ -19451,10 +19444,10 @@ fn zirAlignOf(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].un_node; const operand_src = block.builtinCallArgSrc(inst_data.src_node, 0); const ty = try sema.resolveType(block, operand_src, inst_data.operand); + try sema.ensureLayoutResolved(ty, operand_src); if (ty.isNoReturn(zcu)) { return sema.fail(block, operand_src, "no align available for type '{f}'", .{ty.fmt(sema.pt)}); } - try sema.ensureLayoutResolved(ty, operand_src); return .fromValue(try pt.intValue(.comptime_int, ty.abiAlignment(zcu).toByteUnits().?)); } @@ -20447,10 +20440,10 @@ fn zirReifyUnion( .fields_len = @intCast(fields_len), .layout = layout, .any_field_aligns = any_field_aligns, - .runtime_tag = rt: { - if (explicit_tag_ty != null) break :rt .tagged; - if (layout == .auto and block.wantSafeTypes()) break :rt .safety; - break :rt .none; + .tag_usage = tag: { + if (explicit_tag_ty != null) break :tag .tagged; + if (layout == .auto and block.wantSafeTypes()) break :tag .safety; + break :tag .none; }, .enum_tag_type = if (explicit_tag_ty) |ty| ty.toIntern() else .none, .packed_backing_int_type = if (explicit_packed_backing_type) |ty| ty.toIntern() else .none, @@ -22608,7 +22601,7 @@ fn zirCmpxchg( const result_ty = try pt.optionalType(elem_ty.toIntern()); // special case zero bit types - if (try elem_ty.onePossibleValue(pt) != null) { + if (elem_ty.classify(zcu) == .one_possible_value) { return .fromValue(try pt.nullValue(result_ty)); } @@ -25514,8 +25507,9 @@ fn fieldPtrLoad( const pt = sema.pt; const zcu = pt.zcu; const object_ptr_ty = sema.typeOf(object_ptr); + assert(object_ptr_ty.zigTypeTag(zcu) == .pointer); const pointee_ty = object_ptr_ty.childType(zcu); - try sema.ensureLayoutResolved(pointee_ty, src); // MLUGG TODO + try sema.ensureLayoutResolved(pointee_ty, src); if (try pointee_ty.onePossibleValue(pt)) |opv| { const object: Air.Inst.Ref = .fromValue(opv); return fieldVal(sema, block, src, object, field_name, field_name_src); @@ -26477,9 +26471,9 @@ fn unionFieldPtr( }); const enum_field_index: u32 = @intCast(Type.fromInterned(union_obj.enum_tag_type).enumFieldIndex(field_name, zcu).?); - if (initializing and field_ty.zigTypeTag(zcu) == .noreturn) { + if (initializing and field_ty.classify(zcu) == .no_possible_value) { const msg = msg: { - const msg = try sema.errMsg(src, "cannot initialize 'noreturn' field of union", .{}); + const msg = try sema.errMsg(src, "cannot initialize union field with uninstantiable type '{f}'", .{field_ty.fmt(pt)}); errdefer msg.destroy(sema.gpa); try sema.addFieldErrNote(union_ty, field_index, msg, "field '{f}' declared here", .{ @@ -26529,30 +26523,29 @@ fn unionFieldPtr( }, .@"packed", .@"extern" => {}, } - const field_ptr_val = try union_ptr_val.ptrField(field_index, pt); - return Air.internedToRef(field_ptr_val.toIntern()); + return .fromValue(try union_ptr_val.ptrField(field_index, pt)); } // If the union has a tag, we must either set or or safety check it depending on `initializing`. tag: { if (union_ty.containerLayout(zcu) != .auto) break :tag; const tag_ty: Type = .fromInterned(union_obj.enum_tag_type); - if (try tag_ty.onePossibleValue(pt) != null) break :tag; + if (tag_ty.classify(zcu) == .one_possible_value) break :tag; // There is a hypothetical non-trivial tag. We must set it even if not there at runtime, but // only emit a safety check if it's available at runtime (i.e. it's safety-tagged). const want_tag = try pt.enumValueFieldIndex(tag_ty, enum_field_index); if (initializing) { const set_tag_inst = try block.addBinOp(.set_union_tag, union_ptr, .fromValue(want_tag)); try sema.checkComptimeKnownStore(block, set_tag_inst, .unneeded); // `unneeded` since this isn't a "proper" store - } else if (block.wantSafety() and union_obj.runtime_tag != .none) { - // The tag exists at runtime (safety tag), so emit a safety check. + } else if (block.wantSafety() and union_obj.has_runtime_tag) { + // The tag exists at runtime (actual or safety tag), so emit a safety check. // TODO would it be better if get_union_tag supported pointers to unions? const union_val = try block.addTyOp(.load, union_ty, union_ptr); const active_tag = try block.addTyOp(.get_union_tag, tag_ty, union_val); try sema.addSafetyCheckInactiveUnionField(block, src, active_tag, .fromValue(want_tag)); } } - if (field_ty.zigTypeTag(zcu) == .noreturn) { + if (field_ty.classify(zcu) == .no_possible_value) { _ = try block.addNoOp(.unreach); return .unreachable_value; } @@ -26578,57 +26571,40 @@ fn unionFieldVal( const union_obj = zcu.typeToUnion(union_ty).?; const field_index = try sema.unionFieldIndex(block, union_ty, field_name, field_name_src); const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[field_index]); - const enum_field_index: u32 = @intCast(Type.fromInterned(union_obj.enum_tag_type).enumFieldIndex(field_name, zcu).?); + const enum_tag_ty: Type = .fromInterned(union_obj.enum_tag_type); if (sema.resolveValue(union_byval)) |union_val| { if (union_val.isUndef(zcu)) return pt.undefRef(field_ty); - - const un = ip.indexToKey(union_val.toIntern()).un; - const field_tag = try pt.enumValueFieldIndex(.fromInterned(union_obj.enum_tag_type), enum_field_index); - const tag_matches = un.tag == field_tag.toIntern(); switch (union_obj.layout) { .auto => { - if (tag_matches) { - return Air.internedToRef(un.val); - } else { - const msg = msg: { - const active_index = Type.fromInterned(union_obj.enum_tag_type).enumTagFieldIndex(Value.fromInterned(un.tag), zcu).?; - const active_field_name = Type.fromInterned(union_obj.enum_tag_type).enumFieldName(active_index, zcu); - const msg = try sema.errMsg(src, "access of union field '{f}' while field '{f}' is active", .{ - field_name.fmt(ip), active_field_name.fmt(ip), - }); - errdefer msg.destroy(sema.gpa); - try sema.addDeclaredHereNote(msg, union_ty); - break :msg msg; - }; - return sema.failWithOwnedErrorMsg(block, msg); - } + const active_tag_val = union_val.unionTag(zcu).?; + const active_index = enum_tag_ty.enumTagFieldIndex(active_tag_val, zcu).?; + if (active_index == field_index) return .fromValue(union_val.unionPayload(zcu)); + return sema.fail(block, src, "access of union field '{f}' while field '{f}' is active", .{ + field_name.fmt(ip), enum_tag_ty.enumFieldName(active_index, zcu).fmt(ip), + }); }, - .@"extern" => if (tag_matches) { - // Fast path - no need to use bitcast logic. - return Air.internedToRef(un.val); - } else if (try sema.bitCastVal(union_val, field_ty, 0, 0, 0)) |field_val| { - return Air.internedToRef(field_val.toIntern()); + .@"extern" => if (try sema.bitCastVal(union_val, field_ty, 0, 0, 0)) |field_val| { + return .fromValue(field_val); + } else { + // Runtime-known due to a pointer-to-integer conversion. }, - .@"packed" => if (tag_matches) { - // Fast path - no need to use bitcast logic. - return Air.internedToRef(un.val); - } else if (try sema.bitCastVal(union_val, field_ty, 0, union_ty.bitSize(zcu), 0)) |field_val| { - return Air.internedToRef(field_val.toIntern()); + .@"packed" => { + const field_val = try sema.bitCastVal(union_val, field_ty, 0, union_ty.bitSize(zcu), 0) orelse { + unreachable; // `null` is only possible if the input value contains a pointer, which a packed union cannot. + }; + return .fromValue(field_val); }, } } - if (union_obj.layout == .auto and block.wantSafety() and - union_ty.unionTagTypeSafety(zcu) != null and union_obj.field_types.len > 1) - { - const wanted_tag_val = try pt.enumValueFieldIndex(.fromInterned(union_obj.enum_tag_type), enum_field_index); - const wanted_tag = Air.internedToRef(wanted_tag_val.toIntern()); - const active_tag = try block.addTyOp(.get_union_tag, .fromInterned(union_obj.enum_tag_type), union_byval); - try sema.addSafetyCheckInactiveUnionField(block, src, active_tag, wanted_tag); + if (union_obj.layout == .auto and block.wantSafety() and union_obj.has_runtime_tag) { + const wanted_tag_val = try pt.enumValueFieldIndex(enum_tag_ty, field_index); + const active_tag = try block.addTyOp(.get_union_tag, enum_tag_ty, union_byval); + try sema.addSafetyCheckInactiveUnionField(block, src, active_tag, .fromValue(wanted_tag_val)); } - if (field_ty.zigTypeTag(zcu) == .noreturn) { + if (field_ty.classify(zcu) == .no_possible_value) { _ = try block.addNoOp(.unreach); return .unreachable_value; } @@ -27767,11 +27743,10 @@ fn coerceExtra( }; return Air.internedToRef((try pt.enumValueFieldIndex(dest_ty, @intCast(field_index))).toIntern()); }, - .@"union" => blk: { + .@"union" => if (inst_ty.unionTagType(zcu)) |enum_tag_ty| { // union to its own tag type - const union_tag_ty = inst_ty.unionTagType(zcu) orelse break :blk; - if (union_tag_ty.eql(dest_ty, zcu)) { - return sema.unionToTag(block, dest_ty, inst, inst_src); + if (enum_tag_ty.toIntern() == dest_ty.toIntern()) { + return sema.unionToTag(block, inst); } }, else => {}, @@ -27857,18 +27832,16 @@ fn coerceExtra( else => {}, } - const can_coerce_to = switch (dest_ty.zigTypeTag(zcu)) { - .noreturn, .@"opaque" => false, - else => true, + const dest_is_npv = switch (dest_ty.classify(zcu)) { + .no_possible_value => true, + .one_possible_value => if (inst == .undef) { + return .fromValue((try dest_ty.onePossibleValue(pt)).?); + } else false, + .runtime, .fully_comptime, .partially_comptime => if (inst == .undef) { + return .fromValue(try pt.undefValue(dest_ty)); + } else false, }; - if (can_coerce_to and inst == .undef) { - // undefined to anything. We do this after the big switch above so that - // special logic has a chance to run first, such as `*[N]T` to `[]T` which - // should initialize the length field of the slice. - return .fromValue(try dest_ty.onePossibleValue(pt) orelse try pt.undefValue(dest_ty)); - } - if (!opts.report_err) return error.NotCoercible; if (opts.is_ret and dest_ty.zigTypeTag(zcu) == .noreturn) { @@ -27890,8 +27863,8 @@ fn coerceExtra( const msg = try sema.typeMismatchErrMsg(inst_src, dest_ty, inst_ty); errdefer msg.destroy(sema.gpa); - if (!can_coerce_to) { - try sema.errNote(inst_src, msg, "cannot coerce to '{f}'", .{dest_ty.fmt(pt)}); + if (dest_is_npv) { + try sema.errNote(inst_src, msg, "cannot coerce to uninstantiable type '{f}'", .{dest_ty.fmt(pt)}); } // E!T to T @@ -29099,6 +29072,13 @@ fn storePtr2( }; const maybe_operand_val = sema.resolveValue(operand); + const comptime_only = switch (elem_ty.classify(zcu)) { + .no_possible_value => unreachable, // the coercion should have failed + .one_possible_value => return, // no actual store operation is necessary + .runtime => false, + .partially_comptime, .fully_comptime => true, + }; + const runtime_src = rs: { const ptr_val = try sema.resolveDefinedValue(block, ptr_src, ptr) orelse break :rs ptr_src; if (!sema.isComptimeMutablePtr(ptr_val)) break :rs ptr_src; @@ -29106,16 +29086,9 @@ fn storePtr2( return sema.storePtrVal(block, src, ptr_val, operand_val, elem_ty); }; - // We do this after the possible comptime store above, for the case of field_ptr stores - // to unions because we want the comptime tag to be set, even if the field type is void. - // MLUGG TODO: that's insane, the runtime and comptime sematics should be the same. just set the tag at the same damn time - if (try elem_ty.onePossibleValue(pt) != null) { - return; - } - // We're performing the store at runtime; as such, we need to make sure the pointee type // is not comptime-only. We can hit this case with a `@ptrFromInt` pointer. - if (elem_ty.comptimeOnly(zcu)) { + if (comptime_only) { return sema.failWithOwnedErrorMsg(block, msg: { const msg = try sema.errMsg(src, "cannot store comptime-only type '{f}' at runtime", .{elem_ty.fmt(pt)}); errdefer msg.destroy(sema.gpa); @@ -29477,7 +29450,7 @@ fn coerceEnumToUnion( const enum_ty: Type = .fromInterned(union_obj.enum_tag_type); const enum_obj = ip.loadEnumType(enum_ty.toIntern()); - if (union_obj.runtime_tag != .tagged) return sema.failWithOwnedErrorMsg(block, msg: { + if (union_obj.tag_usage != .tagged) return sema.failWithOwnedErrorMsg(block, msg: { const msg = try sema.typeMismatchErrMsg(inst_src, union_ty, inst_ty); errdefer msg.destroy(sema.gpa); try sema.errNote(union_ty_src, msg, "cannot coerce enum to untagged union", .{}); @@ -29495,31 +29468,35 @@ fn coerceEnumToUnion( const field_name = enum_obj.field_names.get(ip)[field_index]; const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[field_index]); - if (field_ty.zigTypeTag(zcu) == .noreturn) { - const msg = msg: { - const msg = try sema.errMsg(inst_src, "cannot initialize 'noreturn' field of union", .{}); + switch (field_ty.classify(zcu)) { + .one_possible_value => return .fromValue(try pt.unionValue( + union_ty, + val, + (try field_ty.onePossibleValue(pt)).?, + )), + + .no_possible_value => return sema.failWithOwnedErrorMsg(block, msg: { + const msg = try sema.errMsg(inst_src, "cannot initialize union field with uninstantiable type '{f}'", .{field_ty.fmt(pt)}); errdefer msg.destroy(sema.gpa); try sema.addFieldErrNote(union_ty, field_index, msg, "field '{f}' declared here", .{ field_name.fmt(ip), }); try sema.addDeclaredHereNote(msg, union_ty); break :msg msg; - }; - return sema.failWithOwnedErrorMsg(block, msg); + }), + + else => return sema.failWithOwnedErrorMsg(block, msg: { + const msg = try sema.errMsg(inst_src, "coercion from enum '{f}' to union '{f}' must initialize '{f}' field '{f}'", .{ + inst_ty.fmt(pt), union_ty.fmt(pt), + field_ty.fmt(pt), field_name.fmt(ip), + }); + errdefer msg.destroy(sema.gpa); + + try sema.addFieldErrNote(union_ty, field_index, msg, "field '{f}' declared here", .{field_name.fmt(ip)}); + try sema.addDeclaredHereNote(msg, union_ty); + break :msg msg; + }), } - const opv = try field_ty.onePossibleValue(pt) orelse return sema.failWithOwnedErrorMsg(block, msg: { - const msg = try sema.errMsg(inst_src, "coercion from enum '{f}' to union '{f}' must initialize '{f}' field '{f}'", .{ - inst_ty.fmt(pt), union_ty.fmt(pt), - field_ty.fmt(pt), field_name.fmt(ip), - }); - errdefer msg.destroy(sema.gpa); - - try sema.addFieldErrNote(union_ty, field_index, msg, "field '{f}' declared here", .{field_name.fmt(ip)}); - try sema.addDeclaredHereNote(msg, union_ty); - break :msg msg; - }); - - return Air.internedToRef((try pt.unionValue(union_ty, val, opv)).toIntern()); } try sema.requireRuntimeBlock(block, inst_src, null); @@ -29536,32 +29513,14 @@ fn coerceEnumToUnion( return sema.failWithOwnedErrorMsg(block, msg); } - { - var msg: ?*Zcu.ErrorMsg = null; - errdefer if (msg) |some| some.destroy(sema.gpa); - - for (union_obj.field_types.get(ip), 0..) |field_ty, field_index| { - if (Type.fromInterned(field_ty).zigTypeTag(zcu) == .noreturn) { - const err_msg = msg orelse try sema.errMsg( - inst_src, - "runtime coercion from enum '{f}' to union '{f}' which has a 'noreturn' field", - .{ enum_ty.fmt(pt), union_ty.fmt(pt) }, - ); - msg = err_msg; - - try sema.addFieldErrNote(union_ty, field_index, err_msg, "'noreturn' field here", .{}); - } - } - if (msg) |some| { - msg = null; - try sema.addDeclaredHereNote(some, union_ty); - return sema.failWithOwnedErrorMsg(block, some); - } - } - - // If the union has all fields 0 bits, the union value is just the enum value. if (union_ty.unionHasAllZeroBitFieldTypes(zcu)) { - return block.addBitCast(union_ty, enum_tag); + if (try union_ty.onePossibleValue(pt)) |opv| { + // The tag had redundant bits, but we've omitted the tag from the union's runtime layout, so the union is OPV and hence runtime-known. + return .fromValue(opv); + } else { + // The union layout is just the tag, so we can bitcast the enum straight to the union. + return block.addBitCast(union_ty, enum_tag); + } } const msg = msg: { @@ -29575,9 +29534,15 @@ fn coerceEnumToUnion( for (0..union_obj.field_types.len) |field_index| { const field_name = enum_obj.field_names.get(ip)[field_index]; const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[field_index]); - if (try field_ty.onePossibleValue(pt) != null) continue; - try sema.addFieldErrNote(union_ty, field_index, msg, "field '{f}' has type '{f}'", .{ + const ty_description: []const u8 = switch (field_ty.classify(zcu)) { + .one_possible_value => continue, + .no_possible_value => "uninstantiable type", + else => "type", + }; + if (field_ty.classify(zcu) == .one_possible_value) continue; + try sema.addFieldErrNote(union_ty, field_index, msg, "field '{f}' has {s} '{f}'", .{ field_name.fmt(ip), + ty_description, field_ty.fmt(pt), }); } @@ -30345,7 +30310,7 @@ fn resolveIsNonErrFromType( assert(ot == .error_union); const payload_ty = operand_ty.errorUnionPayload(zcu); - if (payload_ty.zigTypeTag(zcu) == .noreturn) { + if (payload_ty.classify(zcu) == .no_possible_value) { return .false; } @@ -31348,24 +31313,28 @@ fn wrapErrorUnionSet( } } -fn unionToTag( - sema: *Sema, - block: *Block, - enum_ty: Type, - un: Air.Inst.Ref, - un_src: LazySrcLoc, -) !Air.Inst.Ref { +/// Returns the enum tag value for the active tag of a tagged union value. +/// +/// Asserts that the type of `un` is a tagged union type. +fn unionToTag(sema: *Sema, block: *Block, un: Air.Inst.Ref) !Air.Inst.Ref { const pt = sema.pt; const zcu = pt.zcu; - if (try enum_ty.onePossibleValue(pt)) |opv| return .fromValue(opv); + const ip = &zcu.intern_pool; + const union_obj = ip.loadUnionType(sema.typeOf(un).toIntern()); + assert(union_obj.tag_usage == .tagged); if (sema.resolveValue(un)) |un_val| { - const tag_val = un_val.unionTag(zcu).?; - if (tag_val.isUndef(zcu)) - return try pt.undefRef(enum_ty); - return Air.internedToRef(tag_val.toIntern()); + return .fromValue(un_val.unionTag(zcu).?); } - try sema.requireRuntimeBlock(block, un_src, null); - return block.addTyOp(.get_union_tag, enum_ty, un); + const enum_tag_ty: Type = .fromInterned(union_obj.enum_tag_type); + if (!union_obj.has_runtime_tag) { + // This means that only one field is possible. + const field_index = for (union_obj.field_types.get(ip), 0..) |field_ty_ip, field_index| { + const field_ty: Type = .fromInterned(field_ty_ip); + if (field_ty.classify(zcu) != .no_possible_value) break field_index; + } else unreachable; + return .fromValue(try pt.enumValueFieldIndex(enum_tag_ty, @intCast(field_index))); + } + return block.addTyOp(.get_union_tag, enum_tag_ty, un); } const PeerResolveStrategy = enum { @@ -33491,15 +33460,6 @@ fn isNoReturn(sema: *Sema, ref: Air.Inst.Ref) bool { return sema.typeOf(ref).isNoReturn(sema.pt.zcu); } -/// Avoids crashing the compiler when asking if inferred allocations are known to be a certain zig type. -fn isKnownZigType(sema: *Sema, ref: Air.Inst.Ref, tag: std.builtin.TypeId) bool { - if (ref.toIndex()) |inst| switch (sema.air_instructions.items(.tag)[@intFromEnum(inst)]) { - .inferred_alloc, .inferred_alloc_comptime => return false, - else => {}, - }; - return sema.typeOf(ref).zigTypeTag(sema.pt.zcu) == tag; -} - pub fn declareDependency(sema: *Sema, dependee: InternPool.Dependee) !void { const pt = sema.pt; if (!pt.zcu.comp.config.incremental) return; @@ -33744,7 +33704,6 @@ fn anyUndef(sema: *Sema, block: *Block, src: LazySrcLoc, val: Value) !bool { const zcu = pt.zcu; return switch (zcu.intern_pool.indexToKey(val.toIntern())) { .undef => true, - .simple_value => |v| v == .undefined, .slice => { // If the slice contents are runtime-known, reification will fail later on with a // specific error message. @@ -33898,7 +33857,6 @@ const ComptimeLoadResult = @import("Sema/comptime_ptr_access.zig").ComptimeLoadR const storeComptimePtr = @import("Sema/comptime_ptr_access.zig").storeComptimePtr; const ComptimeStoreResult = @import("Sema/comptime_ptr_access.zig").ComptimeStoreResult; -// MLUGG TODO: decide how to do the namespacing here pub const type_resolution = @import("Sema/type_resolution.zig"); pub const ensureLayoutResolved = type_resolution.ensureLayoutResolved; pub const ensureStructDefaultsResolved = type_resolution.ensureStructDefaultsResolved; @@ -34313,7 +34271,7 @@ fn zirUnionDecl( .fields_len = @intCast(union_decl.field_names.len), .layout = union_decl.kind.layout(), .any_field_aligns = union_decl.field_align_body_lens != null, - .runtime_tag = switch (union_decl.kind) { + .tag_usage = switch (union_decl.kind) { .auto => if (block.wantSafeTypes()) .safety else .none, .tagged_explicit, diff --git a/src/Sema/bitcast.zig b/src/Sema/bitcast.zig index f06528b6240f4766730353a6dc50d3b8e3c41607..43456c218ba08f987b444d35b8ef5dc8ad053cb7 100644 --- a/src/Sema/bitcast.zig +++ b/src/Sema/bitcast.zig @@ -267,7 +267,6 @@ const UnpackValueBits = struct { .int, .enum_tag, .simple_value, - .empty_enum_value, .float, .ptr, .opt, @@ -453,7 +452,6 @@ const UnpackValueBits = struct { // The only values here with runtime bits are `true` and `false. // These are both 1 bit, so will never need truncating. .simple_value => unreachable, - .empty_enum_value => unreachable, // zero-bit else => unreachable, // zero-bit or not primitives } } diff --git a/src/Sema/type_resolution.zig b/src/Sema/type_resolution.zig index 1096c9cbe7d31cbd32958c0dcdfde5eae03b56bf..50f0f7e3156921fa42cae5e80768d067e245b941 100644 --- a/src/Sema/type_resolution.zig +++ b/src/Sema/type_resolution.zig @@ -72,7 +72,6 @@ pub fn ensureLayoutResolved(sema: *Sema, ty: Type, src: LazySrcLoc) SemaError!vo .error_union, .enum_literal, .enum_tag, - .empty_enum_value, .float, .ptr, .slice, @@ -249,10 +248,10 @@ pub fn resolveStructLayout(sema: *Sema, struct_ty: Type) CompileError!void { // Fields are okay. Now we need to resolve the struct's overall layout (size, field offsets, etc). var any_comptime_fields = false; - var comptime_only = false; - var one_possible_value = true; - var has_runtime_bits = false; var struct_align: Alignment = .@"1"; + var has_no_possible_value = false; + var has_runtime_state = false; + var has_comptime_state = false; // Unlike `struct_obj.field_aligns`, these are not `.none`. const resolved_field_aligns = try sema.arena.alloc(Alignment, struct_obj.field_names.len); for (resolved_field_aligns, 0..) |*align_out, field_idx| { @@ -264,22 +263,37 @@ pub fn resolveStructLayout(sema: *Sema, struct_ty: Type) CompileError!void { } break :a field_ty.defaultStructFieldAlignment(struct_obj.layout, zcu); }; - if (!struct_obj.field_is_comptime_bits.get(ip, field_idx)) { - // Non-`comptime` fields contribute to the struct's layout. - struct_align = struct_align.maxStrict(field_align); - if (field_ty.comptimeOnly(zcu)) comptime_only = true; - if (field_ty.hasRuntimeBits(zcu)) has_runtime_bits = true; - if (try field_ty.onePossibleValue(pt) == null) one_possible_value = false; - if (struct_obj.layout == .auto) { - struct_obj.field_runtime_order.get(ip)[field_idx] = @enumFromInt(field_idx); - } - } else { + align_out.* = field_align; + if (struct_obj.field_is_comptime_bits.get(ip, field_idx)) { assert(struct_obj.layout == .auto); // comptime fields not allowed in extern or packed structs struct_obj.field_runtime_order.get(ip)[field_idx] = .omitted; // comptime fields are not in the runtime order any_comptime_fields = true; + continue; // `comptime` fields do not contribute to the struct layout + } + struct_align = struct_align.maxStrict(field_align); + if (struct_obj.layout == .auto) { + struct_obj.field_runtime_order.get(ip)[field_idx] = @enumFromInt(field_idx); + } + switch (field_ty.classify(zcu)) { + .one_possible_value => {}, + .no_possible_value => has_no_possible_value = true, + .runtime => has_runtime_state = true, + .fully_comptime => has_comptime_state = true, + .partially_comptime => { + has_runtime_state = true; + has_comptime_state = true; + }, } - align_out.* = field_align; } + const class: Type.Class = class: { + if (has_no_possible_value) break :class .no_possible_value; + if (has_comptime_state) { + break :class if (has_runtime_state) .partially_comptime else .fully_comptime; + } else { + break :class if (has_runtime_state) .runtime else .one_possible_value; + } + }; + if (struct_obj.layout == .auto) { const runtime_order = struct_obj.field_runtime_order.get(ip); // This logic does not reorder fields; it only moves the omitted ones to the end so that logic @@ -327,21 +341,21 @@ pub fn resolveStructLayout(sema: *Sema, struct_ty: Type) CompileError!void { struct_obj.field_offsets.get(ip)[field_idx] = @truncate(offset); // truncate because the overflow is handled below cur_offset = offset + field_ty.abiSize(zcu); } - const struct_size = std.math.cast(u32, struct_align.forward(cur_offset)) orelse return sema.fail( - &block, - struct_ty.srcLoc(zcu), - "struct layout requires size {d}, this compiler implementation supports up to {d}", - .{ struct_align.forward(cur_offset), std.math.maxInt(u32) }, - ); + const struct_size: u32 = switch (class) { + .no_possible_value => 0, + else => std.math.cast(u32, struct_align.forward(cur_offset)) orelse return sema.fail( + &block, + struct_ty.srcLoc(zcu), + "struct layout requires size {d}, this compiler implementation supports up to {d}", + .{ struct_align.forward(cur_offset), std.math.maxInt(u32) }, + ), + }; ip.resolveStructLayout( io, struct_ty.toIntern(), struct_size, struct_align, - false, // MLUGG TODO XXX NPV - one_possible_value, - comptime_only, - has_runtime_bits, + class, ); if (any_comptime_fields and !struct_obj.is_reified) { @@ -758,9 +772,8 @@ pub fn resolveUnionLayout(sema: *Sema, union_ty: Type) CompileError!void { // Fields are okay. Now we need to resolve the union's overall layout (size, alignment, etc). var payload_align: Alignment = .@"1"; var payload_size: u64 = 0; - var comptime_only = false; - var has_runtime_bits = union_obj.runtime_tag != .none and enum_tag_ty.hasRuntimeBits(zcu); - var possible_values: enum { none, one, many } = .none; + var possible_tags: u32 = 0; + var payload_has_comptime_state = false; for (0..union_obj.field_types.len) |field_idx| { const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[field_idx]); const field_align: Alignment = a: { @@ -772,21 +785,41 @@ pub fn resolveUnionLayout(sema: *Sema, union_ty: Type) CompileError!void { }; payload_align = payload_align.maxStrict(field_align); payload_size = @max(payload_size, field_ty.abiSize(zcu)); - if (field_ty.comptimeOnly(zcu)) comptime_only = true; - if (field_ty.hasRuntimeBits(zcu)) has_runtime_bits = true; - if (!field_ty.isNoReturn(zcu)) { - if (try field_ty.onePossibleValue(pt) != null) { - possible_values = .many; // this field alone has many possible values - } else switch (possible_values) { - .none => possible_values = .one, // there were none, now there is this field's OPV - .one => possible_values = .many, // there was one, now there are two - .many => {}, - } + + switch (field_ty.classify(zcu)) { + .no_possible_value => {}, // uninstantiable field has no effect + .one_possible_value, .runtime => { + possible_tags += 1; + }, + .partially_comptime, .fully_comptime => { + possible_tags += 1; + payload_has_comptime_state = true; + }, } } + // We only need a runtime tag if there are multiple possible active fields *and* the union is + // not going to be comptime-only. Even if there are still runtime bits in the payload, the tag + // does not require runtime bits in a comptime-only union, because it is impossible to get a + // pointer to a union's tag. + const has_runtime_tag = switch (possible_tags) { + 0, 1 => false, + else => union_obj.tag_usage != .none and !payload_has_comptime_state, + }; + + const class: Type.Class = class: { + if (possible_tags == 0) { + break :class .no_possible_value; + } + if (payload_has_comptime_state) { + break :class if (payload_size > 0) .partially_comptime else .fully_comptime; + } + const have_runtime_bits = has_runtime_tag or payload_size > 0; + break :class if (have_runtime_bits) .runtime else .one_possible_value; + }; + const size: u64, const padding: u64, const alignment: Alignment = layout: { - if (union_obj.runtime_tag == .none) { + if (!has_runtime_tag) { break :layout .{ payload_align.forward(payload_size), 0, payload_align }; } const tag_align = enum_tag_ty.abiAlignment(zcu); @@ -800,6 +833,11 @@ pub fn resolveUnionLayout(sema: *Sema, union_ty: Type) CompileError!void { break :layout .{ size, size - unpadded_size, alignment }; }; + if (class == .no_possible_value or class == .one_possible_value) { + assert(size == 0); + assert(padding == 0); + } + const casted_size = std.math.cast(u32, size) orelse return sema.fail( &block, union_ty.srcLoc(zcu), @@ -810,13 +848,11 @@ pub fn resolveUnionLayout(sema: *Sema, union_ty: Type) CompileError!void { io, union_ty.toIntern(), enum_tag_ty.toIntern(), + class, + has_runtime_tag, casted_size, @intCast(padding), // okay because padding is no greater than size alignment, - possible_values == .none, // MLUGG TODO: make sure queries use `LoadedUnionType.has_no_possible_value`! - possible_values == .one, - comptime_only, - has_runtime_bits, ); } fn failUnionFieldMismatch(sema: *Sema, block: *Block, union_field_names: []const InternPool.NullTerminatedString, enum_tag_ty: Type, enum_obj: *const InternPool.LoadedEnumType) CompileError { diff --git a/src/Type.zig b/src/Type.zig index 9cca3d104320edfc2f0fbf63cc64a30d7256fa0b..8b1c71cc9631a8085c4b8908ce09d1c9d69a057b 100644 --- a/src/Type.zig +++ b/src/Type.zig @@ -23,6 +23,240 @@ pub fn zigTypeTag(ty: Type, zcu: *const Zcu) std.builtin.TypeId { return zcu.intern_pool.zigTypeTag(ty.toIntern()); } +/// Every type is a member of exactly one "class" which determines: +/// * whether values of the type can exist at all +/// * whether values of the type can be runtime-knwon +/// * whether the type is considered comptime-only +/// * whether the type has runtime bits (nonzero ABI size) +pub const Class = enum(u3) { + /// Values of this type cannot exist because the type semantically has no values. Attempting to + /// create a value of this type (such as by coercing `undefined`) always emits a compile error. + /// + /// Not comptime-only. No runtime bits, i.e. ABI size is 0. + /// + /// Exhaustive list of no-possible-value ("NPV") types: + /// * `noreturn` + /// * `anyopaque`, and any `opaque` type + /// * `[n]T` where `n` is non-zero and `T` is NPV + /// * Any tuple where at least one non-`comptime` field has an NPV type + /// * Any enum whose backing type is `noreturn` + /// * Any struct where at least one non-`comptime` field has an NPV type + /// * Any union where every field has an NPV type (including unions with no fields) + /// * If the union would typically have a runtime tag, even if that tag would have runtime + /// bits, the union type is still NPV; the runtime tag is effectively omitted. + no_possible_value, + + /// Values of this type are always comptime-known because there is only one value inhabiting the + /// type. This matches the colloquial understanding of a "zero-bit type". + /// + /// Not comptime-only (although always comptime-known). No runtime bits, i.e. ABI size is 0. + /// + /// Exhaustive list of one-possible-value ("OPV") types: + /// * `void` + /// * `u0`, `i0` + /// * `[0]T` for any `T` + /// * `[n]T` where `T` is OPV + /// * `[n:s]T` where `T` is OPV + /// * `@Vector(0, T)` for any `T` + /// * `@Vector(n, T)` where `T` is OPV + /// * Any tuple where every non-`comptime` field has an OPV type (including tuples with no fields) + /// * Any enum whose backing type is OPV + /// * Any struct where every non-`comptime` field has an OPV type (including structs with no fields) + /// * Any union with no runtime tag where all fields have OPV + /// * Any union where one field has an OPV type, and either: + /// * All other fields have NPV types (in this case, if there would be a runtime tag, it is omitted) + /// * All other fields have NPV or OPV types, and the union has no runtime tag + one_possible_value, + + /// The type holds state (so it is neither NPV nor OPV), but contains no comptime-only state, so + /// values may be runtime-known. + /// + /// Not comptime-only. Has runtime bits, i.e. ABI size is non-zero. + /// + /// Most types which are typically used in Zig inhabit this class. For instance, all pointer + /// types, all integer types other than `u0` and `i0`, and most user-defined aggregates fall + /// into this category. + runtime, + + /// The type holds state (so it is neither NPV nor OPV). Some, but not all, of the contained + /// state is comptime-only. + /// + /// Comptime-only. Has runtime bits, i.e. ABI size is non-zero. + /// + /// Partially-comptime types arise from aggregates (`struct`s, `union`s, or tuples) which have + /// some fields with fully-comptime types (such as `comptime_int`) and some fields with runtime + /// types (such as `u8`). Because the user may acquire pointers to these fields, pointers to the + /// embedded runtime state must be valid, so backends are required to lower the runtime state + /// within the type. + /// + /// Note that logically-runtime state which cannot be directly referenced by the user (such as + /// the enum tag of a tagged union type, or the "populated" bit of an optional type) does not + /// cause a type to be partially-comptime. + partially_comptime, + + /// The type contains exclusively comptime-only state. + /// + /// Comptime-only. No runtime bits, i.e. ABI size is 0. + /// + /// Fully-comptime types arise from a handful of primitive fully-comptime types: + /// * `type` + /// * `comptime_int` + /// * `comptime_float` + /// * `@EnumLiteral()` + /// * `@TypeOf(null)` + /// * `@TypeOf(undefined)` + /// + /// Then, aggregates containing fully-comptime types may themselves be either fully-comptime or + /// partially-comptime; see the doc comment on `.partially_comptime` for details. + fully_comptime, +}; + +/// Returns the `Class` for the type `ty`. Asserts that the layout of `ty` is resolved. +pub fn classify(ty: Type, zcu: *const Zcu) Class { + ty.assertHasLayout(zcu); + const ip = &zcu.intern_pool; + return switch (ip.indexToKey(ty.toIntern())) { + .simple_type => |t| switch (t) { + .f16, + .f32, + .f64, + .f80, + .f128, + .usize, + .isize, + .c_char, + .c_short, + .c_ushort, + .c_int, + .c_uint, + .c_long, + .c_ulong, + .c_longlong, + .c_ulonglong, + .c_longdouble, + .bool, + .anyerror, + .adhoc_inferred_error_set, + => .runtime, + + .anyopaque => .no_possible_value, + + .type, + .comptime_int, + .comptime_float, + .enum_literal, + .null, + .undefined, + => .fully_comptime, + + .void => .one_possible_value, + .noreturn => .no_possible_value, + + .generic_poison => unreachable, + }, + + .error_set_type, + .inferred_error_set_type, + .ptr_type, + .anyframe_type, + => .runtime, + + .func_type => .fully_comptime, + + .opaque_type => .no_possible_value, + + .error_union_type => |eu| switch (Type.fromInterned(eu.payload_type).classify(zcu)) { + .no_possible_value, + .one_possible_value, + .runtime, + => .runtime, + + .partially_comptime => .partially_comptime, + // It may seem that this should be `.partially_comptime` due to the error set, however + // there is no way to take a pointer to the error set of an error union, so it does not + // actually necessitate runtime bits. + .fully_comptime => .fully_comptime, + }, + + .int_type => |int| switch (int.bits) { + 0 => .one_possible_value, + else => .runtime, + }, + .array_type => |arr| { + if (arr.len == 0 and arr.sentinel == .none) return .one_possible_value; + return Type.fromInterned(arr.child).classify(zcu); + }, + .vector_type => |vec| { + if (vec.len == 0) return .one_possible_value; + return Type.fromInterned(vec.child).classify(zcu); + }, + .opt_type => |child| switch (Type.fromInterned(child).classify(zcu)) { + .no_possible_value => .one_possible_value, + .one_possible_value => .runtime, + else => |class| class, + }, + .tuple_type => |tuple| { + var has_runtime_state = false; + var has_comptime_state = false; + for (tuple.types.get(ip), tuple.values.get(ip)) |field_ty, field_comptime_val| { + if (field_comptime_val != .none) continue; + switch (Type.fromInterned(field_ty).classify(zcu)) { + .no_possible_value => return .no_possible_value, + .one_possible_value => {}, + .runtime => has_runtime_state = true, + .fully_comptime => has_comptime_state = true, + .partially_comptime => { + has_runtime_state = true; + has_comptime_state = true; + }, + } + } + if (has_comptime_state) { + return if (has_runtime_state) .partially_comptime else .fully_comptime; + } else { + return if (has_runtime_state) .runtime else .one_possible_value; + } + }, + .struct_type => { + const struct_obj = ip.loadStructType(ty.toIntern()); + return switch (struct_obj.layout) { + .auto, .@"extern" => struct_obj.class, + .@"packed" => Type.fromInterned(struct_obj.packed_backing_int_type).classify(zcu), + }; + }, + .union_type => { + const union_obj = ip.loadUnionType(ty.toIntern()); + return switch (union_obj.layout) { + .auto, .@"extern" => union_obj.class, + .@"packed" => Type.fromInterned(union_obj.packed_backing_int_type).classify(zcu), + }; + }, + .enum_type => Type.fromInterned(ip.loadEnumType(ty.toIntern()).int_tag_type).classify(zcu), + + // values, not types + .undef, + .simple_value, + .variable, + .@"extern", + .func, + .int, + .err, + .error_union, + .enum_literal, + .enum_tag, + .float, + .ptr, + .slice, + .opt, + .aggregate, + .un, + .bitpack, + // memoization, not types + .memoized_call, + => unreachable, + }; +} + /// Asserts the type is resolved. pub fn isSelfComparable(ty: Type, zcu: *const Zcu, is_equality_cmp: bool) bool { return switch (ty.zigTypeTag(zcu)) { @@ -400,7 +634,6 @@ pub fn print(ty: Type, writer: *std.Io.Writer, pt: Zcu.PerThread, ctx: ?*Compari .error_union, .enum_literal, .enum_tag, - .empty_enum_value, .float, .ptr, .slice, @@ -438,116 +671,9 @@ pub fn toValue(self: Type) Value { /// - an enum with an explicit tag type has the ABI size of the integer tag type, /// making it one-possible-value only if the integer tag type has 0 bits. pub fn hasRuntimeBits(ty: Type, zcu: *const Zcu) bool { - ty.assertHasLayout(zcu); - const ip = &zcu.intern_pool; - return switch (ip.indexToKey(ty.toIntern())) { - .int_type => |int_type| int_type.bits != 0, - .ptr_type => true, - .anyframe_type => true, - .array_type => |array_type| array_type.lenIncludingSentinel() > 0 and - Type.fromInterned(array_type.child).hasRuntimeBits(zcu), - .vector_type => |vector_type| vector_type.len > 0 and - Type.fromInterned(vector_type.child).hasRuntimeBits(zcu), - .opt_type => |child| !Type.fromInterned(child).isNoReturn(zcu), - - .error_union_type, - .error_set_type, - .inferred_error_set_type, - => true, - - // These are function *bodies*, not pointers. - // They return false here because they are comptime-only types. - // Special exceptions have to be made when emitting functions due to - // this returning false. - .func_type => false, - - .simple_type => |t| switch (t) { - .f16, - .f32, - .f64, - .f80, - .f128, - .usize, - .isize, - .c_char, - .c_short, - .c_ushort, - .c_int, - .c_uint, - .c_long, - .c_ulong, - .c_longlong, - .c_ulonglong, - .c_longdouble, - .bool, - .anyerror, - .adhoc_inferred_error_set, - .anyopaque, - => true, - - .void, - .noreturn, - => false, - - // primitive comptime-only types - .type, - .comptime_int, - .comptime_float, - .null, - .undefined, - .enum_literal, - => false, - - .generic_poison => unreachable, - }, - .struct_type => { - const struct_obj = ip.loadStructType(ty.toIntern()); - switch (struct_obj.layout) { - .auto, .@"extern" => return struct_obj.has_runtime_bits, - .@"packed" => return Type.fromInterned(struct_obj.packed_backing_int_type).hasRuntimeBits(zcu), - } - }, - .union_type => { - const union_obj = ip.loadUnionType(ty.toIntern()); - switch (union_obj.layout) { - .auto, .@"extern" => return union_obj.has_runtime_bits, - .@"packed" => return Type.fromInterned(union_obj.packed_backing_int_type).hasRuntimeBits(zcu), - } - }, - .tuple_type => |tuple| { - for (tuple.types.get(ip), tuple.values.get(ip)) |field_ty, val| { - if (val != .none) continue; // comptime field - if (Type.fromInterned(field_ty).hasRuntimeBits(zcu)) return true; - } - return false; - }, - - // MLUGG TODO: this answer was already here but... does it actually make sense? - .opaque_type => true, - .enum_type => Type.fromInterned(ip.loadEnumType(ty.toIntern()).int_tag_type).hasRuntimeBits(zcu), - - // values, not types - .undef, - .simple_value, - .variable, - .@"extern", - .func, - .int, - .err, - .error_union, - .enum_literal, - .enum_tag, - .empty_enum_value, - .float, - .ptr, - .slice, - .opt, - .aggregate, - .un, - .bitpack, - // memoization, not types - .memoized_call, - => unreachable, + return switch (ty.classify(zcu)) { + .no_possible_value, .one_possible_value, .fully_comptime => false, + .runtime, .partially_comptime => true, }; } @@ -634,7 +760,6 @@ pub fn hasWellDefinedLayout(ty: Type, zcu: *const Zcu) bool { .error_union, .enum_literal, .enum_tag, - .empty_enum_value, .float, .ptr, .slice, @@ -681,8 +806,12 @@ pub fn isRuntimeFnOrHasRuntimeBits(ty: Type, zcu: *Zcu) bool { } } +/// Returns whether `ty` is NPV, meaning it is "like `noreturn`" in a sense. See doc comments on +/// `Class` for more details. +/// +/// Exactly equivalent to `ty.classify(zcu) == .no_possible_value`. pub fn isNoReturn(ty: Type, zcu: *const Zcu) bool { - return zcu.intern_pool.isNoReturn(ty.toIntern()); + return ty.classify(zcu) == .no_possible_value; } /// Never returns `none`. Asserts that all necessary type resolution is already done. @@ -705,7 +834,10 @@ pub fn ptrAddressSpace(ty: Type, zcu: *const Zcu) std.builtin.AddressSpace { }; } -/// Never returns `none`. Asserts that all necessary type resolution is already done. +/// Never returns `.none`. Asserts that the layout of `ty` is resolved. +/// +/// Unlike ABI size, a type's ABI alignment is not affected by its `Class`. In other words, any +/// alignment is possible regardless of the result of `ty.classify(zcu)`. pub fn abiAlignment(ty: Type, zcu: *const Zcu) Alignment { const ip = &zcu.intern_pool; const target = zcu.getTarget(); @@ -842,7 +974,6 @@ pub fn abiAlignment(ty: Type, zcu: *const Zcu) Alignment { .error_union, .enum_literal, .enum_tag, - .empty_enum_value, .float, .ptr, .slice, @@ -856,7 +987,11 @@ pub fn abiAlignment(ty: Type, zcu: *const Zcu) Alignment { }; } -/// Asserts that `ty` is not an opaque type. +/// Asserts that `ty` is not an opaque type, and that the layout of `ty` is resolved. +/// +/// If the type is NPV, OPV, or fully-comptime (see `Class`), the return value of this function is +/// guaranteed to be zero. Otherwise (if the type is runtime or partially-comptime) the return value +/// is guaranteed to be non-zero. pub fn abiSize(ty: Type, zcu: *const Zcu) u64 { const ip = &zcu.intern_pool; const target = zcu.getTarget(); @@ -883,29 +1018,26 @@ pub fn abiSize(ty: Type, zcu: *const Zcu) u64 { }, .opt_type => |child_ty_ip| { const child_ty: Type = .fromInterned(child_ty_ip); - if (child_ty.isNoReturn(zcu)) return 0; - const child_size = child_ty.abiSize(zcu); - if (ty.optionalReprIsPayload(zcu)) return child_size; + if (child_ty.classify(zcu) == .no_possible_value) return 0; + if (ty.optionalReprIsPayload(zcu)) return child_ty.abiSize(zcu); // Optional types are represented as a struct with the child type as the first // field and a boolean as the second. Since the child type's abi alignment is // guaranteed to be >= that of bool's (1 byte) the added size is exactly equal // to the child type's ABI alignment. - return child_size + child_ty.abiAlignment(zcu).toByteUnits().?; + return child_ty.abiSize(zcu) + child_ty.abiAlignment(zcu).toByteUnits().?; }, .error_set_type, .inferred_error_set_type => errorAbiSize(zcu), .error_union_type => |error_union| { const payload_ty: Type = .fromInterned(error_union.payload_type); - // This code needs to be kept in sync with the equivalent switch prong - // in abiAlignmentInner. - const code_size = errorAbiSize(zcu); - const code_align = errorAbiAlignment(zcu); - const payload_size = payload_ty.abiSize(zcu); - const payload_align = payload_ty.abiAlignment(zcu); + switch (payload_ty.classify(zcu)) { + .fully_comptime => return 0, // error set does not require runtime bits, see comment in `classify` + else => {}, + } // The layout will either be (code, payload, padding) or (payload, code, padding) // depending on which has larger alignment. So the overall size is just the code // and payload sizes added and padded to the larger alignment. - const big_align = code_align.maxStrict(payload_align); - return big_align.forward(payload_size + code_size); + const big_align: Alignment = .maxStrict(errorAbiAlignment(zcu), payload_ty.abiAlignment(zcu)); + return big_align.forward(errorAbiSize(zcu) + payload_ty.abiSize(zcu)); }, .func_type => 0, .simple_type => |t| switch (t) { @@ -946,7 +1078,12 @@ pub fn abiSize(ty: Type, zcu: *const Zcu) u64 { .anyopaque => unreachable, .generic_poison => unreachable, }, - .tuple_type => |tuple| ty.structFieldOffset(tuple.types.len, zcu), + .tuple_type => |tuple| switch (ty.classify(zcu)) { + // `structFieldOffset` is bogus on NPV tuples, because there may be some fields with + // non-zero size. + .no_possible_value => 0, + else => ty.structFieldOffset(tuple.types.len, zcu), + }, .struct_type => { const struct_obj = ip.loadStructType(ty.toIntern()); switch (struct_obj.layout) { @@ -975,7 +1112,6 @@ pub fn abiSize(ty: Type, zcu: *const Zcu) u64 { .error_union, .enum_literal, .enum_tag, - .empty_enum_value, .float, .ptr, .slice, @@ -1100,7 +1236,6 @@ pub fn bitSize(ty: Type, zcu: *const Zcu) u64 { .error_union, .enum_literal, .enum_tag, - .empty_enum_value, .float, .ptr, .slice, @@ -1327,8 +1462,7 @@ pub fn optionalChild(ty: Type, zcu: *const Zcu) Type { } } -/// Returns the tag type of a union, if the type is a union and it has a tag type. -/// Otherwise, returns `null`. +/// If `ty` is a tagged union, returns its tag type. Otherwise, returns `null`. pub fn unionTagType(ty: Type, zcu: *const Zcu) ?Type { assertHasLayout(ty, zcu); const ip = &zcu.intern_pool; @@ -1337,33 +1471,28 @@ pub fn unionTagType(ty: Type, zcu: *const Zcu) ?Type { else => return null, } const union_obj = ip.loadUnionType(ty.toIntern()); - return switch (union_obj.runtime_tag) { + return switch (union_obj.tag_usage) { .tagged => .fromInterned(union_obj.enum_tag_type), .none, .safety => null, }; } -/// Same as `unionTagType` but includes safety tag. -/// Codegen should use this version. -pub fn unionTagTypeSafety(ty: Type, zcu: *const Zcu) ?Type { +/// If the given union type contains a tag (including a safety tag) in its runtime layout, returns +/// its enum tag type. Otherwise, returns null. Asserts that `ty` is a union type. +/// +/// In general, codegen logic should call this function instead of `unionTagType`. +pub fn unionTagTypeRuntime(ty: Type, zcu: *const Zcu) ?Type { assertHasLayout(ty, zcu); - const ip = &zcu.intern_pool; - return switch (ip.indexToKey(ty.toIntern())) { - .union_type => { - const union_type = ip.loadUnionType(ty.toIntern()); - if (union_type.runtime_tag == .none) return null; - return Type.fromInterned(union_type.enum_tag_type); - }, - else => null, - }; + const union_type = zcu.intern_pool.loadUnionType(ty.toIntern()); + if (!union_type.has_runtime_tag) return null; + return .fromInterned(union_type.enum_tag_type); } -/// Asserts the type is a union; returns the tag type, even if the tag will -/// not be stored at runtime. +/// Asserts that `ty` is a union type, and returns its tag type, even if the tag will not be stored at runtime. pub fn unionTagTypeHypothetical(ty: Type, zcu: *const Zcu) Type { assertHasLayout(ty, zcu); - const union_obj = zcu.typeToUnion(ty).?; - return Type.fromInterned(union_obj.enum_tag_type); + const union_obj = zcu.intern_pool.loadUnionType(ty.toIntern()); + return .fromInterned(union_obj.enum_tag_type); } pub fn unionFieldType(ty: Type, enum_tag: Value, zcu: *const Zcu) ?Type { @@ -1573,12 +1702,12 @@ pub fn isUnsignedInt(ty: Type, zcu: *const Zcu) bool { }; } -/// Returns true for integers, enums, error sets, and packed structs. +/// Returns true for integers, enums, error sets, and packed structs/unions. /// If this function returns true, then intInfo() can be called on the type. pub fn isAbiInt(ty: Type, zcu: *const Zcu) bool { return switch (ty.zigTypeTag(zcu)) { .int, .@"enum", .error_set => true, - .@"struct" => ty.containerLayout(zcu) == .@"packed", + .@"struct", .@"union" => ty.containerLayout(zcu) == .@"packed", else => false, }; } @@ -1611,6 +1740,11 @@ pub fn intInfo(starting_ty: Type, zcu: *const Zcu) InternPool.Key.IntType { assert(struct_obj.layout == .@"packed"); ty = .fromInterned(struct_obj.packed_backing_int_type); }, + .union_type => { + const union_obj = ip.loadUnionType(ty.toIntern()); + assert(union_obj.layout == .@"packed"); + ty = .fromInterned(union_obj.packed_backing_int_type); + }, .enum_type => ty = .fromInterned(ip.loadEnumType(ty.toIntern()).int_tag_type), .vector_type => |vector_type| ty = Type.fromInterned(vector_type.child), @@ -1629,7 +1763,6 @@ pub fn intInfo(starting_ty: Type, zcu: *const Zcu) InternPool.Key.IntType { .func_type => unreachable, .simple_type => unreachable, // handled via Index enum tag above - .union_type => unreachable, .opaque_type => unreachable, // values, not types @@ -1643,7 +1776,6 @@ pub fn intInfo(starting_ty: Type, zcu: *const Zcu) InternPool.Key.IntType { .error_union, .enum_literal, .enum_tag, - .empty_enum_value, .float, .ptr, .slice, @@ -1772,321 +1904,181 @@ pub fn isNumeric(ty: Type, zcu: *const Zcu) bool { }; } -/// MLUGG TODO: deal with our friends structs and unions -pub fn onePossibleValue(starting_type: Type, pt: Zcu.PerThread) !?Value { +/// If the type's classification is `Class.one_possible_value` (see `classify`), returns the only +/// possible value for the type. Otherwise, returns `null`. +pub fn onePossibleValue(ty: Type, pt: Zcu.PerThread) !?Value { const zcu = pt.zcu; const comp = zcu.comp; const gpa = comp.gpa; const ip = &zcu.intern_pool; - assertHasLayout(starting_type, zcu); - var ty = starting_type; - while (true) switch (ty.toIntern()) { - .empty_tuple_type => return .empty_tuple, + assertHasLayout(ty, zcu); + return switch (ip.indexToKey(ty.toIntern())) { + .ptr_type, + .error_union_type, + .func_type, + .anyframe_type, + .error_set_type, + .inferred_error_set_type, + .opaque_type, + => null, - else => switch (ip.indexToKey(ty.toIntern())) { - .int_type => |int_type| { - if (int_type.bits == 0) { - return try pt.intValue(ty, 0); - } else { - return null; - } - }, + .simple_type => |t| switch (t) { + .f16, + .f32, + .f64, + .f80, + .f128, + .usize, + .isize, + .c_char, + .c_short, + .c_ushort, + .c_int, + .c_uint, + .c_long, + .c_ulong, + .c_longlong, + .c_ulonglong, + .c_longdouble, + .anyopaque, + .bool, + .type, + .anyerror, + .comptime_int, + .comptime_float, + .enum_literal, + .adhoc_inferred_error_set, + .null, + .undefined, + .noreturn, + => null, - .ptr_type, - .error_union_type, - .func_type, - .anyframe_type, - .error_set_type, - .inferred_error_set_type, - => return null, + .void => .void, - inline .array_type, .vector_type => |seq_type, seq_tag| { - const has_sentinel = seq_tag == .array_type and seq_type.sentinel != .none; - if (seq_type.len + @intFromBool(has_sentinel) == 0) { - return try pt.aggregateValue(ty, &.{}); - } - if (try Type.fromInterned(seq_type.child).onePossibleValue(pt)) |opv| { - return try pt.aggregateSplatValue(ty, opv); - } - return null; - }, - .opt_type => |child| { - if (child == .noreturn_type) { - return try pt.nullValue(ty); - } else { - return null; - } - }, + .generic_poison => unreachable, + }, - .simple_type => |t| switch (t) { - .f16, - .f32, - .f64, - .f80, - .f128, - .usize, - .isize, - .c_char, - .c_short, - .c_ushort, - .c_int, - .c_uint, - .c_long, - .c_ulong, - .c_longlong, - .c_ulonglong, - .c_longdouble, - .anyopaque, - .bool, - .type, - .anyerror, - .comptime_int, - .comptime_float, - .enum_literal, - .adhoc_inferred_error_set, - => return null, + .int_type => |int_type| switch (int_type.bits) { + 0 => try pt.intValue(ty, 0), + else => null, + }, - .void => return .void, - .noreturn => return .@"unreachable", - .null => return .null, - .undefined => return .undef, - - .generic_poison => unreachable, - }, - .struct_type => { - const struct_obj = ip.loadStructType(ty.toIntern()); - if (struct_obj.layout == .@"packed") { + inline .array_type, .vector_type => |seq_type, seq_tag| { + const has_sentinel = seq_tag == .array_type and seq_type.sentinel != .none; + if (seq_type.len + @intFromBool(has_sentinel) == 0) { + return try pt.aggregateValue(ty, &.{}); + } + if (try Type.fromInterned(seq_type.child).onePossibleValue(pt)) |opv| { + return try pt.aggregateSplatValue(ty, opv); + } + return null; + }, + .opt_type => |child| switch (Type.fromInterned(child).classify(zcu)) { + .no_possible_value => try pt.nullValue(ty), + else => null, + }, + .tuple_type => |tuple| { + // Check *whether* the OPV exists first, because constructing it is a little more expensive. + if (ty.classify(zcu) != .one_possible_value) return null; + const field_vals = try zcu.gpa.dupe(InternPool.Index, tuple.values.get(ip)); + defer zcu.gpa.free(field_vals); + for (field_vals, tuple.types.get(ip)) |*field_val, field_ty_ip| { + if (field_val.* != .none) continue; // comptime field value + const field_ty: Type = .fromInterned(field_ty_ip); + field_val.* = (try field_ty.onePossibleValue(pt)).?.toIntern(); + } + return try pt.aggregateValue(ty, field_vals); + }, + .struct_type => { + const struct_obj = ip.loadStructType(ty.toIntern()); + switch (struct_obj.layout) { + .auto, .@"extern" => {}, + .@"packed" => { const backing_ty: Type = .fromInterned(struct_obj.packed_backing_int_type); const backing_val = try backing_ty.onePossibleValue(pt) orelse return null; return try pt.bitpackValue(ty, backing_val); - } else { - if (!struct_obj.has_one_possible_value) return null; + }, + } + // Type resolution already figured out whether there is an OPV, but if there is, it's + // our job to compute it. + if (struct_obj.class != .one_possible_value) return null; + const field_vals = try gpa.alloc(InternPool.Index, struct_obj.field_types.len); + defer gpa.free(field_vals); + for (field_vals, 0..) |*field_val, i_usize| { + const i: u32 = @intCast(i_usize); + if (struct_obj.field_is_comptime_bits.get(ip, i)) { + field_val.* = struct_obj.field_defaults.get(ip)[i]; + assert(field_val.* != .none); + continue; } - // There is an OPV. - const field_vals = try gpa.alloc(InternPool.Index, struct_obj.field_types.len); - defer gpa.free(field_vals); - for (field_vals, 0..) |*field_val, i_usize| { - const i: u32 = @intCast(i_usize); - if (struct_obj.field_is_comptime_bits.get(ip, i)) { - field_val.* = struct_obj.field_defaults.get(ip)[i]; - assert(field_val.* != .none); - continue; - } - const field_ty = Type.fromInterned(struct_obj.field_types.get(ip)[i]); - field_val.* = (try field_ty.onePossibleValue(pt)).?.toIntern(); - } - - // In this case the struct has no runtime-known fields and - // therefore has one possible value. - return try pt.aggregateValue(ty, field_vals); - }, - - .tuple_type => |tuple| { - if (tuple.types.len == 0) { - return try pt.aggregateValue(ty, &.{}); - } - - const field_vals = try zcu.gpa.alloc( - InternPool.Index, - tuple.types.len, - ); - defer zcu.gpa.free(field_vals); - for ( - field_vals, - tuple.types.get(ip), - tuple.values.get(ip), - ) |*field_val, field_ty, field_comptime_val| { - if (field_comptime_val != .none) { - field_val.* = field_comptime_val; - continue; - } - if (try Type.fromInterned(field_ty).onePossibleValue(pt)) |opv| { - field_val.* = opv.toIntern(); - } else return null; - } - - return try pt.aggregateValue(ty, field_vals); - }, - - .union_type => { - const union_obj = ip.loadUnionType(ty.toIntern()); - if (union_obj.layout == .@"packed") { - const backing_ty: Type = .fromInterned(union_obj.packed_backing_int_type); - const backing_val = try backing_ty.onePossibleValue(pt) orelse return null; - return try pt.bitpackValue(ty, backing_val); - } - // MLUGG TODO: is this nonsensical or what!!!!!! - const tag_val = (try Type.fromInterned(union_obj.enum_tag_type).onePossibleValue(pt)) orelse - return null; - if (union_obj.field_types.len == 0) { - const only = try pt.intern(.{ .empty_enum_value = ty.toIntern() }); - return .fromInterned(only); - } - const only_field_ty = union_obj.field_types.get(ip)[0]; - const val_val = (try Type.fromInterned(only_field_ty).onePossibleValue(pt)) orelse - return null; - const only = try pt.internUnion(.{ - .ty = ty.toIntern(), - .tag = tag_val.toIntern(), - .val = val_val.toIntern(), - }); - return .fromInterned(only); - }, - .opaque_type => return null, - .enum_type => { - const enum_obj = ip.loadEnumType(ty.toIntern()); - if (enum_obj.nonexhaustive) { - const int_opv = try Type.fromInterned(enum_obj.int_tag_type).onePossibleValue(pt) orelse return null; - return .fromInterned(try pt.intern(.{ .enum_tag = .{ - .ty = ty.toIntern(), - .int = int_opv.toIntern(), - } })); - } - // MLUGG TODO: this is to preserve existing semantics, i REALLY don't fuck with it... - if (enum_obj.int_tag_type == .comptime_int_type) { - return switch (enum_obj.field_names.len) { - 0 => .fromInterned(try pt.intern(.{ .empty_enum_value = ty.toIntern() })), - 1 => try pt.enumValueFieldIndex(ty, 0), - else => null, - }; - } - const int_tag_opv = try Type.fromInterned(enum_obj.int_tag_type).onePossibleValue(pt) orelse return null; - if (enum_obj.field_names.len == 0) { - return .fromInterned(try pt.intern(.{ .empty_enum_value = ty.toIntern() })); + const field_ty: Type = .fromInterned(struct_obj.field_types.get(ip)[i]); + field_val.* = (try field_ty.onePossibleValue(pt)).?.toIntern(); + } + return try pt.aggregateValue(ty, field_vals); + }, + .union_type => { + const union_obj = ip.loadUnionType(ty.toIntern()); + if (union_obj.layout == .@"packed") { + const backing_ty: Type = .fromInterned(union_obj.packed_backing_int_type); + const backing_val = try backing_ty.onePossibleValue(pt) orelse return null; + return try pt.bitpackValue(ty, backing_val); + } + // Type resolution already figured out whether there is an OPV, but if there is, it's + // our job to compute it. + if (union_obj.class != .one_possible_value) return null; + // The OPV comes from exactly one field whose type is OPV, while all others are NPV. + for (union_obj.field_types.get(ip), 0..) |field_ty_ip, field_index| { + const field_ty: Type = .fromInterned(field_ty_ip); + switch (field_ty.classify(zcu)) { + .no_possible_value => continue, + .one_possible_value => {}, + else => unreachable, } - return .fromInterned(try pt.intern(.{ .enum_tag = .{ - .ty = ty.toIntern(), - .int = int_tag_opv.toIntern(), - } })); - }, - - // values, not types - .undef, - .simple_value, - .variable, - .@"extern", - .func, - .int, - .err, - .error_union, - .enum_literal, - .enum_tag, - .empty_enum_value, - .float, - .ptr, - .slice, - .opt, - .aggregate, - .un, - .bitpack, - // memoization, not types - .memoized_call, - => unreachable, + // This field is the one! + const enum_tag_ty: Type = .fromInterned(union_obj.enum_tag_type); + const tag_val = try pt.enumValueFieldIndex(enum_tag_ty, @intCast(field_index)); + const payload_val = (try field_ty.onePossibleValue(pt)).?; + return try pt.unionValue(ty, tag_val, payload_val); + } else unreachable; }, + .enum_type => if (try ty.intTagType(zcu).onePossibleValue(pt)) |int_tag_opv| { + return .fromInterned(try pt.intern(.{ .enum_tag = .{ + .ty = ty.toIntern(), + .int = int_tag_opv.toIntern(), + } })); + } else null, + + // values, not types + .undef, + .simple_value, + .variable, + .@"extern", + .func, + .int, + .err, + .error_union, + .enum_literal, + .enum_tag, + .float, + .ptr, + .slice, + .opt, + .aggregate, + .un, + .bitpack, + // memoization, not types + .memoized_call, + => unreachable, }; } /// Asserts that `ty` has its layout resolved. `generic_poison` will return `false`. pub fn comptimeOnly(ty: Type, zcu: *const Zcu) bool { - const ip = &zcu.intern_pool; - return switch (ip.indexToKey(ty.toIntern())) { - .array_type => |array_type| return Type.fromInterned(array_type.child).comptimeOnly(zcu), - .vector_type => |vector_type| return Type.fromInterned(vector_type.child).comptimeOnly(zcu), - .opt_type => |child| return Type.fromInterned(child).comptimeOnly(zcu), - .error_union_type => |error_union_type| return Type.fromInterned(error_union_type.payload_type).comptimeOnly(zcu), - .enum_type => return Type.fromInterned(ip.loadEnumType(ty.toIntern()).int_tag_type).comptimeOnly(zcu), - - .int_type, - .ptr_type, - .anyframe_type, - .error_set_type, - .inferred_error_set_type, - .opaque_type, - => false, - - // These are function bodies, not function pointers. - .func_type => true, - - .simple_type => |t| switch (t) { - .f16, - .f32, - .f64, - .f80, - .f128, - .usize, - .isize, - .c_char, - .c_short, - .c_ushort, - .c_int, - .c_uint, - .c_long, - .c_ulong, - .c_longlong, - .c_ulonglong, - .c_longdouble, - .anyopaque, - .bool, - .void, - .anyerror, - .adhoc_inferred_error_set, - .noreturn, - .generic_poison, - => false, - - .type, - .comptime_int, - .comptime_float, - .null, - .undefined, - .enum_literal, - => true, - }, - .struct_type => { - const struct_obj = ip.loadStructType(ty.toIntern()); - return switch (struct_obj.layout) { - .@"packed" => false, - .auto, .@"extern" => struct_obj.comptime_only, - }; - }, - .union_type => { - const union_obj = ip.loadUnionType(ty.toIntern()); - return switch (union_obj.layout) { - .@"packed" => false, - .auto, .@"extern" => union_obj.comptime_only, - }; - }, - .tuple_type => |tuple| { - for (tuple.types.get(ip), tuple.values.get(ip)) |field_ty, val| { - if (val != .none) continue; - if (!Type.fromInterned(field_ty).comptimeOnly(zcu)) continue; - return true; - } - return false; - }, - - // values, not types - .undef, - .simple_value, - .variable, - .@"extern", - .func, - .int, - .err, - .error_union, - .enum_literal, - .enum_tag, - .empty_enum_value, - .float, - .ptr, - .slice, - .opt, - .aggregate, - .un, - .bitpack, - // memoization, not types - .memoized_call, - => unreachable, + if (ty.toIntern() == .generic_poison_type) return false; + if (ty.zigTypeTag(zcu) == .error_union and ty.errorUnionPayload(zcu).toIntern() == .generic_poison_type) return false; + return switch (ty.classify(zcu)) { + .no_possible_value, .one_possible_value, .runtime => false, + .partially_comptime, .fully_comptime => true, }; } @@ -2286,8 +2278,8 @@ pub fn enumFieldIndex(ty: Type, field_name: InternPool.NullTerminatedString, zcu return enum_type.nameIndex(ip, field_name); } -/// Asserts `ty` is an enum. `enum_tag` can either be `enum_field_index` or -/// an integer which represents the enum value. Returns the field index in +/// Asserts `ty` is an enum. `enum_tag` can either be the actual enum tag value +/// or an integer which represents the enum value. Returns the field index in /// declaration order, or `null` if `enum_tag` does not match any field. pub fn enumTagFieldIndex(ty: Type, enum_tag: Value, zcu: *const Zcu) ?u32 { assertHasLayout(ty, zcu); @@ -2327,7 +2319,7 @@ pub fn structFieldCount(ty: Type, zcu: *const Zcu) u32 { } } -/// Returns the field type. Supports structs and unions. +/// Returns the field type. Supports tuples, structs, and unions. pub fn fieldType(ty: Type, index: usize, zcu: *const Zcu) Type { const ip = &zcu.intern_pool; const types = switch (ip.indexToKey(ty.toIntern())) { @@ -2493,7 +2485,7 @@ pub fn structFieldOffset(ty: Type, index: usize, zcu: *const Zcu) u64 { for (tuple.types.get(ip), tuple.values.get(ip), 0..) |field_ty, field_val, i| { if (field_val != .none or !Type.fromInterned(field_ty).hasRuntimeBits(zcu)) { // comptime field - if (i == index) return offset; + if (i == index) return 0; continue; } @@ -2509,8 +2501,7 @@ pub fn structFieldOffset(ty: Type, index: usize, zcu: *const Zcu) u64 { .union_type => { const union_type = ip.loadUnionType(ty.toIntern()); - if (union_type.runtime_tag == .none) - return 0; + if (!union_type.has_runtime_tag) return 0; const layout = Type.getUnionLayout(union_type, zcu); if (layout.tag_align.compare(.gte, layout.payload_align)) { // {Tag, Payload} @@ -2746,7 +2737,7 @@ pub fn getUnionLayout(loaded_union: InternPool.LoadedUnionType, zcu: *const Zcu) } payload_align = payload_align.max(field_align); } - if (loaded_union.runtime_tag == .none or + if (!loaded_union.has_runtime_tag or !Type.fromInterned(loaded_union.enum_tag_type).hasRuntimeBits(zcu)) { return .{ @@ -2872,11 +2863,19 @@ pub fn containerTypeName(ty: Type, ip: *const InternPool) InternPool.NullTermina } /// Returns `true` if a value of this type is always `null`. -/// Returns `false` if a value of this type is neve `null`. +/// Returns `false` if a value of this type is never `null`. /// Returns `null` otherwise. pub fn isNullFromType(ty: Type, zcu: *const Zcu) ?bool { if (ty.zigTypeTag(zcu) != .optional and !ty.isCPtr(zcu)) return false; - if (ty.optionalChild(zcu).isNoReturn(zcu)) return true; // `?noreturn` is always null + const payload_ty = ty.optionalChild(zcu); + if (payload_ty.classify(zcu) == .no_possible_value) return true; // `?noreturn` etc + + // Although it has runtime bits, `?error{}` is always null. MLUGG TODO: think for a bit... + switch (zcu.intern_pool.indexToKey(payload_ty.toIntern())) { + .error_set_type => |error_set| if (error_set.names.len == 0) return true, + else => {}, + } + return null; } @@ -3096,7 +3095,6 @@ pub fn assertHasLayout(ty: Type, zcu: *const Zcu) void { .error_union, .enum_literal, .enum_tag, - .empty_enum_value, .float, .ptr, .slice, @@ -3175,7 +3173,6 @@ fn collectSubtypes(ty: Type, pt: Zcu.PerThread, visited: *std.AutoArrayHashMapUn .error_union, .enum_literal, .enum_tag, - .empty_enum_value, .float, .ptr, .slice, diff --git a/src/Value.zig b/src/Value.zig index 774a6758ceff8d82fc579f580b33de17ad4fef75..d158aa558c1c63a5cab26832774270680a61e5e7 100644 --- a/src/Value.zig +++ b/src/Value.zig @@ -348,7 +348,7 @@ pub fn writeToMemory(val: Value, pt: Zcu.PerThread, buffer: []u8) error{ } else { const backing_ty = try ty.externUnionBackingType(pt); const byte_count: usize = @intCast(backing_ty.abiSize(zcu)); - return writeToMemory(val.unionValue(zcu), pt, buffer[0..byte_count]); + return writeToMemory(val.unionPayload(zcu), pt, buffer[0..byte_count]); } }, .@"packed" => { @@ -746,7 +746,6 @@ pub fn compareScalar( /// Returns `false` if the value or any vector element is undefined. /// /// Note that `!compareAllWithZero(.eq, ...) != compareAllWithZero(.neq, ...)` -/// TODO MLUGG: lowkey wanna delete this pub fn compareAllWithZero(lhs: Value, op: std.math.CompareOperator, zcu: *Zcu) bool { return switch (zcu.intern_pool.indexToKey(lhs.toIntern())) { .float => |float| switch (float.storage) { @@ -919,7 +918,7 @@ pub fn unionTag(val: Value, zcu: *Zcu) ?Value { }; } -pub fn unionValue(val: Value, zcu: *Zcu) Value { +pub fn unionPayload(val: Value, zcu: *Zcu) Value { return switch (zcu.intern_pool.indexToKey(val.toIntern())) { .un => |un| Value.fromInterned(un.val), else => unreachable, @@ -2442,7 +2441,7 @@ pub fn interpret(val: Value, comptime T: type, pt: Zcu.PerThread) error{ OutOfMe inline else => |tag_comptime| @unionInit( T, @tagName(tag_comptime), - try val.unionValue(zcu).interpret(@FieldType(T, @tagName(tag_comptime)), pt), + try val.unionPayload(zcu).interpret(@FieldType(T, @tagName(tag_comptime)), pt), ), }; }, diff --git a/src/Zcu/PerThread.zig b/src/Zcu/PerThread.zig index 186d6147d13622463f56d708f07a62d8b0e128c2..46e275045ac39d580acc8bf33dd45e671d19d39f 100644 --- a/src/Zcu/PerThread.zig +++ b/src/Zcu/PerThread.zig @@ -3825,9 +3825,7 @@ pub fn enumValueFieldIndex(pt: Zcu.PerThread, ty: Type, field_index: u32) Alloca pub fn undefValue(pt: Zcu.PerThread, ty: Type) Allocator.Error!Value { if (std.debug.runtime_safety) { - if (try ty.onePossibleValue(pt)) |opv| { - assert(opv.isUndef(pt.zcu)); - } + assert(ty.classify(pt.zcu) != .one_possible_value); } return .fromInterned(try pt.intern(.{ .undef = ty.toIntern() })); } @@ -3909,10 +3907,7 @@ pub fn aggregateValue(pt: Zcu.PerThread, ty: Type, elems: []const InternPool.Ind for (elems) |elem| { if (!Value.fromInterned(elem).isUndef(pt.zcu)) break; } else if (elems.len > 0) { - // All undef, so return an undef struct. However, don't use `undefValue`, because its - // non-OPV assertion can loop on `[1]@TypeOf(undefined)`: that type has an OPV of - // `.{undefined}`, which here we normalize to `undefined`. - return .fromInterned(try pt.intern(.{ .undef = ty.toIntern() })); + return pt.undefValue(ty); } return .fromInterned(try pt.intern(.{ .aggregate = .{ .ty = ty.toIntern(), diff --git a/src/codegen.zig b/src/codegen.zig index 9edb90fb51056d9ad56f913982b001ff8750e52b..45b70dee2ac19600117a0a08eec0087b234c7013 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -343,7 +343,6 @@ pub fn generateSymbol( .undef => unreachable, // handled above .simple_value => |simple_value| switch (simple_value) { - .undefined => unreachable, // non-runtime value .void => unreachable, // non-runtime value .null => unreachable, // non-runtime value .@"unreachable" => unreachable, // non-runtime value @@ -357,7 +356,6 @@ pub fn generateSymbol( .@"extern", .func, .enum_literal, - .empty_enum_value, => unreachable, // non-runtime values .int => { const abi_size = math.cast(usize, ty.abiSize(zcu)) orelse return error.Overflow; diff --git a/src/codegen/aarch64/Select.zig b/src/codegen/aarch64/Select.zig index f9ff7874770ed1bd1b995a3689a89242c7211898..a95faca765385b3b4b879b0cf5b0e7885adf4e49 100644 --- a/src/codegen/aarch64/Select.zig +++ b/src/codegen/aarch64/Select.zig @@ -10588,7 +10588,6 @@ pub const Value = struct { .error_union, .enum_literal, .enum_tag, - .empty_enum_value, .float, .ptr, .slice, @@ -10711,7 +10710,6 @@ pub const Value = struct { .inferred_error_set_type, .enum_literal, - .empty_enum_value, .memoized_call, => unreachable, // not a runtime value .undef => break :free try isel.emit(if (mat.ra.isVector()) .movi(switch (size) { @@ -10732,7 +10730,7 @@ pub const Value = struct { } }), }), .simple_value => |simple_value| switch (simple_value) { - .undefined, .void, .null, .@"unreachable" => unreachable, + .void, .null, .@"unreachable" => unreachable, .true => continue :constant_key .{ .int = .{ .ty = .bool_type, .storage = .{ .u64 = 1 }, @@ -11408,7 +11406,6 @@ fn writeKeyToMemory(isel: *Select, constant_key: InternPool.Key, buffer: []u8) e .inferred_error_set_type, .enum_literal, - .empty_enum_value, .memoized_call, => unreachable, // not a runtime value .err => |err| { @@ -12085,7 +12082,7 @@ pub const CallAbiIterator = struct { const zcu = isel.pt.zcu; const ip = &zcu.intern_pool; - if (ty.isNoReturn(zcu) or !ty.hasRuntimeBitsIgnoreComptime(zcu)) return null; + if (!ty.hasRuntimeBitsIgnoreComptime(zcu)) return null; try isel.values.ensureUnusedCapacity(zcu.gpa, Value.max_parts); const wip_vi = isel.initValue(ty); type_key: switch (ip.indexToKey(ty.toIntern())) { @@ -12326,7 +12323,6 @@ pub const CallAbiIterator = struct { .error_union, .enum_literal, .enum_tag, - .empty_enum_value, .float, .ptr, .slice, diff --git a/src/codegen/c.zig b/src/codegen/c.zig index 69c4e91d999c4340e0fe18f299ba88787052a66a..f0df1aa1c0d0f072944a1f6d6dbd2bfc5f462f75 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -1040,7 +1040,6 @@ pub const DeclGen = struct { .undef => unreachable, // handled above .simple_value => |simple_value| switch (simple_value) { // non-runtime values - .undefined => unreachable, .void => unreachable, .null => unreachable, .@"unreachable" => unreachable, @@ -1052,7 +1051,6 @@ pub const DeclGen = struct { .@"extern", .func, .enum_literal, - .empty_enum_value, => unreachable, // non-runtime values .int => |int| switch (int.storage) { .u64, .i64, .big_int => try w.print("{f}", .{try dg.fmtIntLiteralDec(val, location)}), @@ -1756,7 +1754,6 @@ pub const DeclGen = struct { .error_union, .enum_literal, .enum_tag, - .empty_enum_value, .float, .ptr, .slice, @@ -5848,7 +5845,7 @@ fn fieldLocation( .auto, .@"extern" => { const field_ty: Type = .fromInterned(loaded_union.field_types.get(ip)[field_index]); if (!field_ty.hasRuntimeBitsIgnoreComptime(zcu)) - return if (loaded_union.hasTag(ip) and !container_ty.unionHasAllZeroBitFieldTypes(zcu)) + return if (loaded_union.has_runtime_tag and !container_ty.unionHasAllZeroBitFieldTypes(zcu)) .{ .field = .{ .identifier = "payload" } } else .begin; @@ -7022,7 +7019,7 @@ fn airSetUnionTag(f: *Function, inst: Air.Inst.Index) !CValue { const union_ty = f.typeOf(bin_op.lhs).childType(zcu); const layout = union_ty.unionGetLayout(zcu); if (layout.tag_size == 0) return .none; - const tag_ty = union_ty.unionTagTypeSafety(zcu).?; + const tag_ty = union_ty.unionTagTypeRuntime(zcu).?; const w = &f.object.code.writer; const a = try Assignment.start(f, w, try f.ctypeFromType(tag_ty, .complete)); @@ -7462,18 +7459,15 @@ fn airUnionInit(f: *Function, inst: Air.Inst.Index) !CValue { const local = try f.allocLocal(inst, union_ty); - const field: CValue = if (union_ty.unionTagTypeSafety(zcu)) |tag_ty| field: { - const layout = union_ty.unionGetLayout(zcu); - if (layout.tag_size != 0) { - const field_index = tag_ty.enumFieldIndex(field_name, zcu).?; - const tag_val = try pt.enumValueFieldIndex(tag_ty, field_index); - - const a = try Assignment.start(f, w, try f.ctypeFromType(tag_ty, .complete)); - try f.writeCValueMember(w, local, .{ .identifier = "tag" }); - try a.assign(f, w); - try w.print("{f}", .{try f.fmtIntLiteralDec(try tag_val.intFromEnum(tag_ty, pt))}); - try a.end(f, w); - } + const field: CValue = if (union_ty.unionTagTypeRuntime(zcu)) |tag_ty| field: { + assert(union_ty.unionGetLayout(zcu).tag_size != 0); + const field_index = tag_ty.enumFieldIndex(field_name, zcu).?; + const tag_val = try pt.enumValueFieldIndex(tag_ty, field_index); + const a = try Assignment.start(f, w, try f.ctypeFromType(tag_ty, .complete)); + try f.writeCValueMember(w, local, .{ .identifier = "tag" }); + try a.assign(f, w); + try w.print("{f}", .{try f.fmtIntLiteralDec(try tag_val.intFromEnum(tag_ty, pt))}); + try a.end(f, w); break :field .{ .payload_identifier = field_name.toSlice(ip) }; } else .{ .identifier = field_name.toSlice(ip) }; diff --git a/src/codegen/c/Type.zig b/src/codegen/c/Type.zig index 0bcdb207fc693b3acb38cd2a2c458dee9d61dbe3..3ee61a90b656759d0848af6f74e0ab313f9c34c2 100644 --- a/src/codegen/c/Type.zig +++ b/src/codegen/c/Type.zig @@ -2479,7 +2479,7 @@ pub const Pool = struct { return pool.fromFields(allocator, .@"struct", &fields, kind); }, .opt_type => |payload_type| { - if (ip.isNoReturn(payload_type)) return .void; + if (Type.fromInterned(payload_type).isNoReturn(zcu)) return .void; const payload_ctype = try pool.fromType( allocator, scratch, @@ -2521,7 +2521,7 @@ pub const Pool = struct { .signedness = .unsigned, .bits = error_set_bits, }, mod, kind); - if (ip.isNoReturn(error_union_info.payload_type)) return error_set_ctype; + if (Type.fromInterned(error_union_info.payload_type).isNoReturn(zcu)) return error_set_ctype; const payload_type = Type.fromInterned(error_union_info.payload_type); const payload_ctype = try pool.fromType( allocator, @@ -2684,9 +2684,8 @@ pub const Pool = struct { const loaded_union = ip.loadUnionType(ip_index); switch (loaded_union.flagsUnordered(ip).layout) { .auto, .@"extern" => { - const has_tag = loaded_union.hasTag(ip); const fwd_decl = try pool.getFwdDecl(allocator, .{ - .tag = if (has_tag) .@"struct" else .@"union", + .tag = if (loaded_union.has_runtime_tag) .@"struct" else .@"union", .name = .{ .index = ip_index }, }); if (kind.isForward()) return if (ty.hasRuntimeBitsIgnoreComptime(zcu)) @@ -2707,7 +2706,7 @@ pub const Pool = struct { const field_type = Type.fromInterned( loaded_union.field_types.get(ip)[field_index], ); - if (ip.isNoReturn(field_type.toIntern())) continue; + if (field_type.isNoReturn(zcu)) continue; const field_ctype = try pool.fromType( allocator, scratch, @@ -2738,7 +2737,7 @@ pub const Pool = struct { scratch.items.len - scratch_top, @typeInfo(Field).@"struct".fields.len, )); - if (!has_tag) { + if (!loaded_union.has_runtime_tag) { if (fields_len == 0) return .void; try pool.ensureUnusedCapacity(allocator, 1); const extra_index = try pool.addHashedExtra( @@ -2836,7 +2835,7 @@ pub const Pool = struct { var hasher = Hasher.init; const return_type = Type.fromInterned(func_info.return_type); const return_ctype: CType = - if (!ip.isNoReturn(func_info.return_type)) try pool.fromType( + if (!Type.fromInterned(func_info.return_type).isNoReturn(zcu)) try pool.fromType( allocator, scratch, return_type, @@ -2889,7 +2888,6 @@ pub const Pool = struct { .error_union, .enum_literal, .enum_tag, - .empty_enum_value, .float, .ptr, .slice, diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 695bb82133aea39b4478c3a9ad4f4ec4c07be6cc..b952f8c22567527c10c76afcd7c8970481418263 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -3516,7 +3516,6 @@ pub const Object = struct { .error_union, .enum_literal, .enum_tag, - .empty_enum_value, .float, .ptr, .slice, @@ -3722,7 +3721,6 @@ pub const Object = struct { .undef => unreachable, // handled above .simple_value => |simple_value| switch (simple_value) { - .undefined => unreachable, // non-runtime value .void => unreachable, // non-runtime value .null => unreachable, // non-runtime value .@"unreachable" => unreachable, // non-runtime value @@ -3732,7 +3730,6 @@ pub const Object = struct { }, .variable, .enum_literal, - .empty_enum_value, => unreachable, // non-runtime values .@"extern" => |@"extern"| { const function_index = try o.resolveLlvmFunction(pt, @"extern".owner_nav); diff --git a/src/codegen/spirv/CodeGen.zig b/src/codegen/spirv/CodeGen.zig index 709de66a412e409ea6017d0a15a631156fe133e4..217581a72ce28eb36f6f0a08a78640124ad2e293 100644 --- a/src/codegen/spirv/CodeGen.zig +++ b/src/codegen/spirv/CodeGen.zig @@ -814,11 +814,9 @@ fn constant(cg: *CodeGen, ty: Type, val: Value, repr: Repr) Error!Id { .@"extern", .func, .enum_literal, - .empty_enum_value, => unreachable, // non-runtime values .simple_value => |simple_value| switch (simple_value) { - .undefined, .void, .null, .@"unreachable", @@ -4482,7 +4480,7 @@ fn airSetUnionTag(cg: *CodeGen, inst: Air.Inst.Index) !void { if (layout.tag_size == 0) return; - const tag_ty = un_ty.unionTagTypeSafety(zcu).?; + const tag_ty = un_ty.unionTagTypeRuntime(zcu).?; const tag_ty_id = try cg.resolveType(tag_ty, .indirect); const tag_ptr_ty_id = try cg.module.ptrType(tag_ty_id, cg.module.storageClass(un_ptr_ty.ptrAddressSpace(zcu))); @@ -4508,7 +4506,7 @@ fn airGetUnionTag(cg: *CodeGen, inst: Air.Inst.Index) !?Id { const union_handle = try cg.resolve(ty_op.operand); if (!layout.has_payload) return union_handle; - const tag_ty = un_ty.unionTagTypeSafety(zcu).?; + const tag_ty = un_ty.unionTagTypeRuntime(zcu).?; return try cg.extractField(tag_ty, union_handle, layout.tag_index); } diff --git a/src/codegen/wasm/CodeGen.zig b/src/codegen/wasm/CodeGen.zig index cdcaac93025a4902f1d123c485e80076558a84ed..6b5cd3c1c556aaf63343a2fe33e4966f96203a01 100644 --- a/src/codegen/wasm/CodeGen.zig +++ b/src/codegen/wasm/CodeGen.zig @@ -1244,7 +1244,7 @@ fn generateInner(cg: *CodeGen, any_returns: bool) InnerError!Mir { if (any_returns and cg.air.instructions.len > 0) { const inst: Air.Inst.Index = @enumFromInt(cg.air.instructions.len - 1); const last_inst_ty = cg.typeOfIndex(inst); - if (!last_inst_ty.hasRuntimeBitsIgnoreComptime(zcu) or last_inst_ty.isNoReturn(zcu)) { + if (!last_inst_ty.hasRuntimeBitsIgnoreComptime(zcu)) { try cg.addTag(.@"unreachable"); } } @@ -2201,9 +2201,6 @@ fn airCall(cg: *CodeGen, inst: Air.Inst.Index, modifier: std.builtin.CallModifie const result_value = result_value: { if (!ret_ty.hasRuntimeBitsIgnoreComptime(zcu) and !ret_ty.isError(zcu)) { break :result_value .none; - } else if (ret_ty.isNoReturn(zcu)) { - try cg.addTag(.@"unreachable"); - break :result_value .none; } else if (first_param_sret) { break :result_value sret; } else if (zcu.typeToFunc(fn_ty).?.cc == .wasm_mvp) { @@ -3158,7 +3155,6 @@ fn lowerConstant(cg: *CodeGen, val: Value, ty: Type) InnerError!WValue { .undef => unreachable, // handled above .simple_value => |simple_value| switch (simple_value) { - .undefined, .void, .null, .@"unreachable", @@ -3173,7 +3169,6 @@ fn lowerConstant(cg: *CodeGen, val: Value, ty: Type) InnerError!WValue { .@"extern", .func, .enum_literal, - .empty_enum_value, => unreachable, // non-runtime values .int => { const int_info = ty.intInfo(zcu); @@ -5340,7 +5335,7 @@ fn airUnionInit(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void { const field_name = union_obj.loadTagType(ip).names.get(ip)[extra.field_index]; const tag_int = blk: { - const tag_ty = union_ty.unionTagTypeHypothetical(zcu); + const tag_ty = union_ty.unionTagTypeRuntime(zcu).?; const enum_field_index = tag_ty.enumFieldIndex(field_name, zcu).?; const tag_val = try pt.enumValueFieldIndex(tag_ty, enum_field_index); break :blk try cg.lowerConstant(tag_val, tag_ty); @@ -7109,9 +7104,6 @@ fn callIntrinsic( if (!return_type.hasRuntimeBitsIgnoreComptime(zcu)) { return .none; - } else if (return_type.isNoReturn(zcu)) { - try cg.addTag(.@"unreachable"); - return .none; } else if (want_sret_param) { return sret; } else { diff --git a/src/codegen/x86_64/CodeGen.zig b/src/codegen/x86_64/CodeGen.zig index 2a7558505b3c4b1bedad6466d7c15452534c78c1..f46f87ccff5846df798fc470fbb97e06c3cd59e7 100644 --- a/src/codegen/x86_64/CodeGen.zig +++ b/src/codegen/x86_64/CodeGen.zig @@ -171467,7 +171467,7 @@ fn genBody(cg: *CodeGen, body: []const Air.Inst.Index) InnerError!void { const union_layout = union_ty.unionGetLayout(zcu); if (union_layout.tag_size > 0) { var tag_temp = try cg.tempFromValue(try pt.enumValueFieldIndex( - union_ty.unionTagTypeSafety(zcu).?, + union_ty.unionTagTypeRuntime(zcu).?, union_init.field_index, )); try res.write(&tag_temp, .{ diff --git a/src/link/Dwarf.zig b/src/link/Dwarf.zig index 027d5391a56231bc342f6cfe7e8d55a5a08379fa..1d87a42601427050245a3c1f6b2e314b515b0d0b 100644 --- a/src/link/Dwarf.zig +++ b/src/link/Dwarf.zig @@ -1603,7 +1603,7 @@ pub const WipNav = struct { const zcu = pt.zcu; const ty = val.typeOf(zcu); const has_runtime_bits = ty.hasRuntimeBits(zcu); - const has_comptime_state = ty.comptimeOnly(zcu) and try ty.onePossibleValue(pt) == null; + const has_comptime_state = ty.comptimeOnly(zcu); try wip_nav.abbrevCode(if (has_runtime_bits and has_comptime_state) switch (tag) { .comptime_arg => if (opt_name) |_| .comptime_arg_runtime_bits_comptime_state else .unnamed_comptime_arg_runtime_bits_comptime_state, .local_const => if (opt_name) |_| .local_const_runtime_bits_comptime_state else unreachable, @@ -2108,7 +2108,7 @@ pub const WipNav = struct { const zcu = wip_nav.pt.zcu; const ip = &zcu.intern_pool; const ty = value.typeOf(zcu); - if (std.debug.runtime_safety) assert(ty.comptimeOnly(zcu) and try ty.onePossibleValue(wip_nav.pt) == null); + if (std.debug.runtime_safety) assert(ty.comptimeOnly(zcu)); if (ty.toIntern() == .type_type) return wip_nav.getTypeEntry(value.toType()); if (ip.isFunctionType(ty.toIntern()) and !value.isUndef(zcu)) return wip_nav.getNavEntry(switch (ip.indexToKey(value.toIntern())) { else => unreachable, @@ -2705,7 +2705,7 @@ fn initWipNavInner( try wip_nav.refType(.fromInterned(if (maybe_func_type) |func_type| func_type.return_type else @"extern".ty)); if (maybe_func_type) |func_type| { try wip_nav.infoAddrSym(sym_index, 0); - try diw.writeByte(@intFromBool(ip.isNoReturn(func_type.return_type))); + try diw.writeByte(@intFromBool(Type.fromInterned(func_type.return_type).isNoReturn(zcu))); if (func_type.param_types.len > 0 or func_type.is_var_args) { for (func_type.param_types.get(ip)) |param_type| { try wip_nav.abbrevCode(.extern_param); @@ -2733,7 +2733,7 @@ fn initWipNavInner( try wip_nav.strp(@"extern".name.toSlice(ip)); try wip_nav.refType(.fromInterned(func_type.return_type)); try wip_nav.infoAddrSym(sym_index, 0); - try diw.writeByte(@intFromBool(ip.isNoReturn(func_type.return_type))); + try diw.writeByte(@intFromBool(Type.fromInterned(func_type.return_type).isNoReturn(zcu))); if (func_type.param_types.len > 0 or func_type.is_var_args) { for (func_type.param_types.get(ip)) |param_type| { try wip_nav.abbrevCode(.extern_param); @@ -2818,7 +2818,7 @@ fn initWipNavInner( else => |a| a.maxStrict(target_info.minFunctionAlignment(target)), }.toByteUnits().?); try diw.writeByte(@intFromBool(decl.linkage != .normal)); - try diw.writeByte(@intFromBool(ip.isNoReturn(func_type.return_type))); + try diw.writeByte(@intFromBool(Type.fromInterned(func_type.return_type).isNoReturn(zcu))); const dlw = &wip_nav.debug_line.writer; try dlw.writeByte(DW.LNS.extended_op); @@ -3172,7 +3172,7 @@ fn updateComptimeNavInner(dwarf: *Dwarf, pt: Zcu.PerThread, nav_index: InternPoo .none => .{ false, false }, else => .{ field_type.hasRuntimeBits(zcu), - field_type.comptimeOnly(zcu) and try field_type.onePossibleValue(pt) == null, + field_type.comptimeOnly(zcu), }, }; try wip_nav.abbrevCode(if (is_comptime) @@ -3294,7 +3294,7 @@ fn updateComptimeNavInner(dwarf: *Dwarf, pt: Zcu.PerThread, nav_index: InternPoo try diw.writeUleb128(union_layout.abi_size); try diw.writeUleb128(union_layout.abi_align.toByteUnits().?); const loaded_tag = ip.loadEnumType(loaded_union.enum_tag_type); - if (loaded_union.runtime_tag != .none) { + if (loaded_union.has_runtime_tag) { try wip_nav.abbrevCode(.tagged_union); try wip_nav.infoSectionOffset( .debug_info, @@ -3371,7 +3371,6 @@ fn updateComptimeNavInner(dwarf: *Dwarf, pt: Zcu.PerThread, nav_index: InternPoo .error_union, .enum_literal, .enum_tag, - .empty_enum_value, .float, .ptr, .slice, @@ -3465,7 +3464,7 @@ fn updateComptimeNavInner(dwarf: *Dwarf, pt: Zcu.PerThread, nav_index: InternPoo const diw = &wip_nav.debug_info.writer; const nav_ty = nav_val.typeOf(zcu); const has_runtime_bits = nav_ty.hasRuntimeBits(zcu); - const has_comptime_state = nav_ty.comptimeOnly(zcu) and try nav_ty.onePossibleValue(pt) == null; + const has_comptime_state = nav_ty.comptimeOnly(zcu); try wip_nav.declCommon(if (has_runtime_bits and has_comptime_state) .{ .decl = .decl_const_runtime_bits_comptime_state, .generic_decl = .generic_decl_const, @@ -3845,7 +3844,7 @@ fn updateLazyType( .none => .{ false, false }, else => .{ field_type.hasRuntimeBits(zcu), - field_type.comptimeOnly(zcu) and try field_type.onePossibleValue(pt) == null, + field_type.comptimeOnly(zcu), }, }; try wip_nav.abbrevCode(if (has_comptime_state) @@ -4008,7 +4007,6 @@ fn updateLazyType( .error_union, .enum_literal, .enum_tag, - .empty_enum_value, .float, .ptr, .slice, @@ -4128,7 +4126,7 @@ fn updateLazyValue( .payload => |payload_val| { const payload_type: Type = .fromInterned(ip.typeOf(payload_val)); const has_runtime_bits = payload_type.hasRuntimeBits(zcu); - const has_comptime_state = payload_type.comptimeOnly(zcu) and try payload_type.onePossibleValue(pt) == null; + const has_comptime_state = payload_type.comptimeOnly(zcu); try wip_nav.abbrevCode(if (has_comptime_state) .comptime_value_field_comptime_state else if (has_runtime_bits) @@ -4164,7 +4162,6 @@ fn updateLazyValue( }, .fromInterned(int.ty), Value.fromInterned(value_index).toBigInt(&big_int_space, zcu)); try wip_nav.refType(.fromInterned(enum_tag.ty)); }, - .empty_enum_value => unreachable, .float => |float| { switch (float.storage) { .f16 => |f16_val| { @@ -4209,7 +4206,7 @@ fn updateLazyValue( .comptime_alloc, .comptime_field => unreachable, .uav => |uav| { const uav_ty: Type = .fromInterned(ip.typeOf(uav.val)); - if (try uav_ty.onePossibleValue(pt)) |_| { + if (uav_ty.classify(zcu) == .one_possible_value) { try wip_nav.abbrevCode(if (zero_bit_accesses.items.len > 0) .aggregate_udata_comptime_value else @@ -4337,7 +4334,7 @@ fn updateLazyValue( } if (opt.val != .none) child_field: { const has_runtime_bits = opt_child_type.hasRuntimeBits(zcu); - const has_comptime_state = opt_child_type.comptimeOnly(zcu) and try opt_child_type.onePossibleValue(pt) == null; + const has_comptime_state = opt_child_type.comptimeOnly(zcu); try wip_nav.abbrevCode(if (has_comptime_state) .comptime_value_field_comptime_state else if (has_runtime_bits) @@ -4363,7 +4360,7 @@ fn updateLazyValue( if (loaded_struct_type.field_is_comptime_bits.get(ip, field_index)) continue; const field_type: Type = .fromInterned(loaded_struct_type.field_types.get(ip)[field_index]); const has_runtime_bits = field_type.hasRuntimeBits(zcu); - const has_comptime_state = field_type.comptimeOnly(zcu) and try field_type.onePossibleValue(pt) == null; + const has_comptime_state = field_type.comptimeOnly(zcu); try wip_nav.abbrevCode(if (has_comptime_state) .comptime_value_field_comptime_state else if (has_runtime_bits) @@ -4386,7 +4383,7 @@ fn updateLazyValue( if (tuple_type.values.get(ip)[field_index] != .none) continue; const field_type: Type = .fromInterned(tuple_type.types.get(ip)[field_index]); const has_runtime_bits = field_type.hasRuntimeBits(zcu); - const has_comptime_state = field_type.comptimeOnly(zcu) and try field_type.onePossibleValue(pt) == null; + const has_comptime_state = field_type.comptimeOnly(zcu); try wip_nav.abbrevCode(if (has_comptime_state) .comptime_value_field_comptime_state else if (has_runtime_bits) @@ -4411,7 +4408,7 @@ fn updateLazyValue( inline .array_type, .vector_type => |sequence_type| { const child_type: Type = .fromInterned(sequence_type.child); const has_runtime_bits = child_type.hasRuntimeBits(zcu); - const has_comptime_state = child_type.comptimeOnly(zcu) and try child_type.onePossibleValue(pt) == null; + const has_comptime_state = child_type.comptimeOnly(zcu); for (switch (aggregate.storage) { .bytes => unreachable, .elems => |elems| elems, @@ -4443,7 +4440,7 @@ fn updateLazyValue( const field_ty: Type = .fromInterned(loaded_union_type.field_types.get(ip)[field_index]); const field_name = ip.loadEnumType(loaded_union_type.enum_tag_type).field_names.get(ip)[field_index]; const has_runtime_bits = field_ty.hasRuntimeBits(zcu); - const has_comptime_state = field_ty.comptimeOnly(zcu) and try field_ty.onePossibleValue(pt) == null; + const has_comptime_state = field_ty.comptimeOnly(zcu); try wip_nav.abbrevCode(if (has_comptime_state) .comptime_value_field_comptime_state else if (has_runtime_bits) @@ -4540,7 +4537,7 @@ fn updateContainerTypeWriterError( .none => .{ false, false }, else => .{ field_type.hasRuntimeBits(zcu), - field_type.comptimeOnly(zcu) and try field_type.onePossibleValue(pt) == null, + field_type.comptimeOnly(zcu), }, }; try wip_nav.abbrevCode(if (is_comptime) @@ -4647,7 +4644,7 @@ fn updateContainerTypeWriterError( .none => .{ false, false }, else => .{ field_type.hasRuntimeBits(zcu), - field_type.comptimeOnly(zcu) and try field_type.onePossibleValue(pt) == null, + field_type.comptimeOnly(zcu), }, }; try wip_nav.abbrevCode(if (is_comptime) @@ -4724,7 +4721,7 @@ fn updateContainerTypeWriterError( try diw.writeUleb128(union_layout.abi_size); try diw.writeUleb128(union_layout.abi_align.toByteUnits().?); const loaded_tag = ip.loadEnumType(loaded_union.enum_tag_type); - if (loaded_union.runtime_tag != .none) { + if (loaded_union.has_runtime_tag) { try wip_nav.abbrevCode(.tagged_union); try wip_nav.infoSectionOffset( .debug_info, diff --git a/src/print_value.zig b/src/print_value.zig index d472472481350ff57a47ed3277c6a54cc161d374..d05fe09ec15416b21e5adfe7324f6375fd6d6ff5 100644 --- a/src/print_value.zig +++ b/src/print_value.zig @@ -73,7 +73,6 @@ pub fn print( .simple_value => |simple_value| switch (simple_value) { .void => try writer.writeAll("{}"), - .undefined, .null, .true, .false, @@ -111,7 +110,6 @@ pub fn print( try print(Value.fromInterned(enum_tag.int), writer, level - 1, pt, opt_sema); try writer.writeAll(")"); }, - .empty_enum_value => try writer.writeAll("(empty enum value)"), .float => |float| switch (float.storage) { inline else => |x| try writer.print("{d}", .{@as(f64, @floatCast(x))}), }, -- 2.54.0