stage2: explicitly tagged enums no longer have one possible value

Previously, Zig had inconsistent semantics for an enum like this:

`enum(u8){zero = 0}`

Although in theory this can only hold one possible value, the tag
`zero`, Zig no longer will treat the type this way. It will do loads and
stores, as if the type has runtime bits.

Closes #12619

Tests passed locally:
 * test-behavior
 * test-cases
This commit is contained in:
Andrew Kelley 2022-08-24 20:27:11 -07:00
parent af19909b9c
commit 7453f56e67
10 changed files with 113 additions and 60 deletions

View File

@ -3,7 +3,7 @@ const io = std.io;
const os = std.os; const os = std.os;
const math = std.math; const math = std.math;
const mem = std.mem; const mem = std.mem;
const debug = std.debug; const assert = std.debug.assert;
const File = std.fs.File; const File = std.fs.File;
const native_endian = @import("builtin").target.cpu.arch.endian(); const native_endian = @import("builtin").target.cpu.arch.endian();
@ -872,14 +872,14 @@ pub const Elf_MIPS_ABIFlags_v0 = extern struct {
}; };
comptime { comptime {
debug.assert(@sizeOf(Elf32_Ehdr) == 52); assert(@sizeOf(Elf32_Ehdr) == 52);
debug.assert(@sizeOf(Elf64_Ehdr) == 64); assert(@sizeOf(Elf64_Ehdr) == 64);
debug.assert(@sizeOf(Elf32_Phdr) == 32); assert(@sizeOf(Elf32_Phdr) == 32);
debug.assert(@sizeOf(Elf64_Phdr) == 56); assert(@sizeOf(Elf64_Phdr) == 56);
debug.assert(@sizeOf(Elf32_Shdr) == 40); assert(@sizeOf(Elf32_Shdr) == 40);
debug.assert(@sizeOf(Elf64_Shdr) == 64); assert(@sizeOf(Elf64_Shdr) == 64);
} }
pub const Auxv = switch (@sizeOf(usize)) { pub const Auxv = switch (@sizeOf(usize)) {

View File

@ -1368,18 +1368,20 @@ pub const Union = struct {
} }
} }
payload_align = @maximum(payload_align, 1); payload_align = @maximum(payload_align, 1);
if (!have_tag or fields.len <= 1) return .{ if (!have_tag or !u.tag_ty.hasRuntimeBits()) {
.abi_size = std.mem.alignForwardGeneric(u64, payload_size, payload_align), return .{
.abi_align = payload_align, .abi_size = std.mem.alignForwardGeneric(u64, payload_size, payload_align),
.most_aligned_field = most_aligned_field, .abi_align = payload_align,
.most_aligned_field_size = most_aligned_field_size, .most_aligned_field = most_aligned_field,
.biggest_field = biggest_field, .most_aligned_field_size = most_aligned_field_size,
.payload_size = payload_size, .biggest_field = biggest_field,
.payload_align = payload_align, .payload_size = payload_size,
.tag_align = 0, .payload_align = payload_align,
.tag_size = 0, .tag_align = 0,
.padding = 0, .tag_size = 0,
}; .padding = 0,
};
}
// Put the tag before or after the payload depending on which one's // Put the tag before or after the payload depending on which one's
// alignment is greater. // alignment is greater.
const tag_size = u.tag_ty.abiSize(target); const tag_size = u.tag_ty.abiSize(target);

View File

@ -28844,6 +28844,10 @@ pub fn typeHasOnePossibleValue(
.enum_numbered => { .enum_numbered => {
const resolved_ty = try sema.resolveTypeFields(block, src, ty); const resolved_ty = try sema.resolveTypeFields(block, src, ty);
const enum_obj = resolved_ty.castTag(.enum_numbered).?.data; const enum_obj = resolved_ty.castTag(.enum_numbered).?.data;
// An explicit tag type is always provided for enum_numbered.
if (enum_obj.tag_ty.hasRuntimeBits()) {
return null;
}
if (enum_obj.fields.count() == 1) { if (enum_obj.fields.count() == 1) {
if (enum_obj.values.count() == 0) { if (enum_obj.values.count() == 0) {
return Value.zero; // auto-numbered return Value.zero; // auto-numbered
@ -28857,6 +28861,9 @@ pub fn typeHasOnePossibleValue(
.enum_full => { .enum_full => {
const resolved_ty = try sema.resolveTypeFields(block, src, ty); const resolved_ty = try sema.resolveTypeFields(block, src, ty);
const enum_obj = resolved_ty.castTag(.enum_full).?.data; const enum_obj = resolved_ty.castTag(.enum_full).?.data;
if (enum_obj.tag_ty.hasRuntimeBits()) {
return null;
}
if (enum_obj.fields.count() == 1) { if (enum_obj.fields.count() == 1) {
if (enum_obj.values.count() == 0) { if (enum_obj.values.count() == 0) {
return Value.zero; // auto-numbered return Value.zero; // auto-numbered

View File

@ -6524,13 +6524,13 @@ fn airCmpxchg(self: *Self, inst: Air.Inst.Index) !void {
const extra = self.air.extraData(Air.Block, ty_pl.payload); const extra = self.air.extraData(Air.Block, ty_pl.payload);
_ = ty_pl; _ = ty_pl;
_ = extra; _ = extra;
return self.fail("TODO implement airCmpxchg for {}", .{self.target.cpu.arch}); return self.fail("TODO implement x86 airCmpxchg", .{});
// return self.finishAir(inst, result, .{ extra.ptr, extra.expected_value, extra.new_value }); // return self.finishAir(inst, result, .{ extra.ptr, extra.expected_value, extra.new_value });
} }
fn airAtomicRmw(self: *Self, inst: Air.Inst.Index) !void { fn airAtomicRmw(self: *Self, inst: Air.Inst.Index) !void {
_ = inst; _ = inst;
return self.fail("TODO implement airCmpxchg for {}", .{self.target.cpu.arch}); return self.fail("TODO implement x86 airAtomicRaw", .{});
} }
fn airAtomicLoad(self: *Self, inst: Air.Inst.Index) !void { fn airAtomicLoad(self: *Self, inst: Air.Inst.Index) !void {

View File

@ -1860,7 +1860,7 @@ pub const Object = struct {
var offset: u64 = 0; var offset: u64 = 0;
for (fields.values()) |field, i| { for (fields.values()) |field, i| {
if (field.is_comptime or !field.ty.hasRuntimeBitsIgnoreComptime()) continue; if (field.is_comptime or !field.ty.hasRuntimeBits()) continue;
const field_size = field.ty.abiSize(target); const field_size = field.ty.abiSize(target);
const field_align = field.alignment(target, layout); const field_align = field.alignment(target, layout);
@ -2764,7 +2764,7 @@ pub const DeclGen = struct {
var any_underaligned_fields = false; var any_underaligned_fields = false;
for (struct_obj.fields.values()) |field| { for (struct_obj.fields.values()) |field| {
if (field.is_comptime or !field.ty.hasRuntimeBitsIgnoreComptime()) continue; if (field.is_comptime or !field.ty.hasRuntimeBits()) continue;
const field_align = field.alignment(target, struct_obj.layout); const field_align = field.alignment(target, struct_obj.layout);
const field_ty_align = field.ty.abiAlignment(target); const field_ty_align = field.ty.abiAlignment(target);
@ -3443,7 +3443,7 @@ pub const DeclGen = struct {
var need_unnamed = false; var need_unnamed = false;
for (struct_obj.fields.values()) |field, i| { for (struct_obj.fields.values()) |field, i| {
if (field.is_comptime or !field.ty.hasRuntimeBitsIgnoreComptime()) continue; if (field.is_comptime or !field.ty.hasRuntimeBits()) continue;
const field_align = field.alignment(target, struct_obj.layout); const field_align = field.alignment(target, struct_obj.layout);
big_align = @maximum(big_align, field_align); big_align = @maximum(big_align, field_align);
@ -9477,7 +9477,7 @@ fn llvmFieldIndex(
var llvm_field_index: c_uint = 0; var llvm_field_index: c_uint = 0;
for (ty.structFields().values()) |field, i| { for (ty.structFields().values()) |field, i| {
if (field.is_comptime or !field.ty.hasRuntimeBitsIgnoreComptime()) continue; if (field.is_comptime or !field.ty.hasRuntimeBits()) continue;
const field_align = field.alignment(target, layout); const field_align = field.alignment(target, layout);
big_align = @maximum(big_align, field_align); big_align = @maximum(big_align, field_align);

View File

@ -2310,6 +2310,8 @@ pub const Type = extern union {
/// fields will count towards the ABI size. For example, `struct {T: type, x: i32}` /// fields will count towards the ABI size. For example, `struct {T: type, x: i32}`
/// hasRuntimeBits()=true and abiSize()=4 /// hasRuntimeBits()=true and abiSize()=4
/// * the type has only one possible value, making its ABI size 0. /// * the type has only one possible value, making its ABI size 0.
/// - 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.
/// When `ignore_comptime_only` is true, then types that are comptime only /// When `ignore_comptime_only` is true, then types that are comptime only
/// may return false positives. /// may return false positives.
pub fn hasRuntimeBitsAdvanced( pub fn hasRuntimeBitsAdvanced(
@ -2452,9 +2454,9 @@ pub const Type = extern union {
_ = try sk.sema.resolveTypeFields(sk.block, sk.src, ty); _ = try sk.sema.resolveTypeFields(sk.block, sk.src, ty);
} }
assert(struct_obj.haveFieldTypes()); assert(struct_obj.haveFieldTypes());
for (struct_obj.fields.values()) |value| { for (struct_obj.fields.values()) |field| {
if (value.is_comptime) continue; if (field.is_comptime) continue;
if (try value.ty.hasRuntimeBitsAdvanced(ignore_comptime_only, sema_kit)) if (try field.ty.hasRuntimeBitsAdvanced(ignore_comptime_only, sema_kit))
return true; return true;
} else { } else {
return false; return false;
@ -2463,7 +2465,7 @@ pub const Type = extern union {
.enum_full => { .enum_full => {
const enum_full = ty.castTag(.enum_full).?.data; const enum_full = ty.castTag(.enum_full).?.data;
return enum_full.fields.count() >= 2; return enum_full.tag_ty.hasRuntimeBitsAdvanced(ignore_comptime_only, sema_kit);
}, },
.enum_simple => { .enum_simple => {
const enum_simple = ty.castTag(.enum_simple).?.data; const enum_simple = ty.castTag(.enum_simple).?.data;
@ -2490,9 +2492,10 @@ pub const Type = extern union {
}, },
.union_safety_tagged, .union_tagged => { .union_safety_tagged, .union_tagged => {
const union_obj = ty.cast(Payload.Union).?.data; const union_obj = ty.cast(Payload.Union).?.data;
if (union_obj.fields.count() > 0 and try union_obj.tag_ty.hasRuntimeBitsAdvanced(ignore_comptime_only, sema_kit)) { if (try union_obj.tag_ty.hasRuntimeBitsAdvanced(ignore_comptime_only, sema_kit)) {
return true; return true;
} }
if (sema_kit) |sk| { if (sema_kit) |sk| {
_ = try sk.sema.resolveTypeFields(sk.block, sk.src, ty); _ = try sk.sema.resolveTypeFields(sk.block, sk.src, ty);
} }
@ -3125,7 +3128,11 @@ pub const Type = extern union {
.lazy => |arena| return AbiAlignmentAdvanced{ .val = try Value.Tag.lazy_align.create(arena, ty) }, .lazy => |arena| return AbiAlignmentAdvanced{ .val = try Value.Tag.lazy_align.create(arena, ty) },
}; };
if (union_obj.fields.count() == 0) { if (union_obj.fields.count() == 0) {
return AbiAlignmentAdvanced{ .scalar = @boolToInt(union_obj.layout == .Extern) }; if (have_tag) {
return abiAlignmentAdvanced(union_obj.tag_ty, target, strat);
} else {
return AbiAlignmentAdvanced{ .scalar = @boolToInt(union_obj.layout == .Extern) };
}
} }
var max_align: u32 = 0; var max_align: u32 = 0;
@ -4991,14 +4998,18 @@ pub const Type = extern union {
.enum_numbered => { .enum_numbered => {
const enum_numbered = ty.castTag(.enum_numbered).?.data; const enum_numbered = ty.castTag(.enum_numbered).?.data;
if (enum_numbered.fields.count() == 1) { // An explicit tag type is always provided for enum_numbered.
return enum_numbered.values.keys()[0]; if (enum_numbered.tag_ty.hasRuntimeBits()) {
} else {
return null; return null;
} }
assert(enum_numbered.fields.count() == 1);
return enum_numbered.values.keys()[0];
}, },
.enum_full => { .enum_full => {
const enum_full = ty.castTag(.enum_full).?.data; const enum_full = ty.castTag(.enum_full).?.data;
if (enum_full.tag_ty.hasRuntimeBits()) {
return null;
}
if (enum_full.fields.count() == 1) { if (enum_full.fields.count() == 1) {
if (enum_full.values.count() == 0) { if (enum_full.values.count() == 0) {
return Value.zero; return Value.zero;
@ -5333,7 +5344,8 @@ pub const Type = extern union {
.enum_numbered => return ty.castTag(.enum_numbered).?.data.tag_ty, .enum_numbered => return ty.castTag(.enum_numbered).?.data.tag_ty,
.enum_simple => { .enum_simple => {
const enum_simple = ty.castTag(.enum_simple).?.data; const enum_simple = ty.castTag(.enum_simple).?.data;
const bits = std.math.log2_int_ceil(usize, enum_simple.fields.count()); const field_count = enum_simple.fields.count();
const bits: u16 = if (field_count == 0) 0 else std.math.log2_int_ceil(usize, field_count);
buffer.* = .{ buffer.* = .{
.base = .{ .tag = .int_unsigned }, .base = .{ .tag = .int_unsigned },
.data = bits, .data = bits,
@ -5653,19 +5665,22 @@ pub const Type = extern union {
target: Target, target: Target,
pub fn next(it: *StructOffsetIterator) ?FieldOffset { pub fn next(it: *StructOffsetIterator) ?FieldOffset {
if (it.struct_obj.fields.count() <= it.field) const i = it.field;
if (it.struct_obj.fields.count() <= i)
return null; return null;
const field = it.struct_obj.fields.values()[it.field]; const field = it.struct_obj.fields.values()[i];
defer it.field += 1; it.field += 1;
if (!field.ty.hasRuntimeBits() or field.is_comptime)
return FieldOffset{ .field = it.field, .offset = it.offset }; if (field.is_comptime or !field.ty.hasRuntimeBits()) {
return FieldOffset{ .field = i, .offset = it.offset };
}
const field_align = field.alignment(it.target, it.struct_obj.layout); const field_align = field.alignment(it.target, it.struct_obj.layout);
it.big_align = @maximum(it.big_align, field_align); it.big_align = @maximum(it.big_align, field_align);
it.offset = std.mem.alignForwardGeneric(u64, it.offset, field_align); const field_offset = std.mem.alignForwardGeneric(u64, it.offset, field_align);
defer it.offset += field.ty.abiSize(it.target); it.offset = field_offset + field.ty.abiSize(it.target);
return FieldOffset{ .field = it.field, .offset = it.offset }; return FieldOffset{ .field = i, .offset = field_offset };
} }
}; };

View File

@ -26,7 +26,6 @@ test {
_ = @import("behavior/bugs/920.zig"); _ = @import("behavior/bugs/920.zig");
_ = @import("behavior/bugs/1025.zig"); _ = @import("behavior/bugs/1025.zig");
_ = @import("behavior/bugs/1076.zig"); _ = @import("behavior/bugs/1076.zig");
_ = @import("behavior/bugs/1111.zig");
_ = @import("behavior/bugs/1277.zig"); _ = @import("behavior/bugs/1277.zig");
_ = @import("behavior/bugs/1310.zig"); _ = @import("behavior/bugs/1310.zig");
_ = @import("behavior/bugs/1381.zig"); _ = @import("behavior/bugs/1381.zig");

View File

@ -1,11 +0,0 @@
const Foo = enum(c_int) {
Bar = -1,
};
test "issue 1111 fixed" {
const v = Foo.Bar;
switch (v) {
Foo.Bar => return,
}
}

View File

@ -1,6 +1,7 @@
const builtin = @import("builtin"); const builtin = @import("builtin");
const std = @import("std"); const std = @import("std");
const expect = std.testing.expect; const expect = std.testing.expect;
const assert = std.debug.assert;
const mem = std.mem; const mem = std.mem;
const Tag = std.meta.Tag; const Tag = std.meta.Tag;
@ -1128,3 +1129,44 @@ test "tag name functions are unique" {
_ = a; _ = a;
} }
} }
test "size of enum with only one tag which has explicit integer tag type" {
if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest;
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
const E = enum(u8) { nope = 10 };
const S0 = struct { e: E };
const S1 = extern struct { e: E };
//const U = union(E) { nope: void };
comptime assert(@sizeOf(E) == 1);
comptime assert(@sizeOf(S0) == 1);
comptime assert(@sizeOf(S1) == 1);
//comptime assert(@sizeOf(U) == 1);
var s1: S1 = undefined;
s1.e = .nope;
try expect(s1.e == .nope);
const ptr = @ptrCast(*u8, &s1);
try expect(ptr.* == 10);
var s0: S0 = undefined;
s0.e = .nope;
try expect(s0.e == .nope);
}
test "switch on an extern enum with negative value" {
// TODO x86, wasm backends fail because they assume that enum tag types are unsigned
if (@import("builtin").zig_backend == .stage2_x86_64) return error.SkipZigTest;
if (@import("builtin").zig_backend == .stage2_wasm) return error.SkipZigTest;
const Foo = enum(c_int) {
Bar = -1,
};
const v = Foo.Bar;
switch (v) {
Foo.Bar => return,
}
}

View File

@ -1,6 +1,7 @@
const builtin = @import("builtin"); const builtin = @import("builtin");
const std = @import("std"); const std = @import("std");
const expect = std.testing.expect; const expect = std.testing.expect;
const assert = std.debug.assert;
const expectEqual = std.testing.expectEqual; const expectEqual = std.testing.expectEqual;
const Tag = std.meta.Tag; const Tag = std.meta.Tag;
@ -1065,6 +1066,8 @@ test "@unionInit on union with tag but no fields" {
if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
const S = struct { const S = struct {
const Type = enum(u8) { no_op = 105 }; const Type = enum(u8) { no_op = 105 };
@ -1079,11 +1082,7 @@ test "@unionInit on union with tag but no fields" {
}; };
comptime { comptime {
if (builtin.zig_backend == .stage1) { assert(@sizeOf(Data) == 1);
// stage1 gets the wrong answer here
} else {
std.debug.assert(@sizeOf(Data) == 0);
}
} }
fn doTheTest() !void { fn doTheTest() !void {