Commit 7453f56e67

Andrew Kelley <andrew@ziglang.org>
2022-08-25 05:27:11
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
1 parent af19909
Changed files (10)
lib/std/elf.zig
@@ -3,7 +3,7 @@ const io = std.io;
 const os = std.os;
 const math = std.math;
 const mem = std.mem;
-const debug = std.debug;
+const assert = std.debug.assert;
 const File = std.fs.File;
 const native_endian = @import("builtin").target.cpu.arch.endian();
 
@@ -872,14 +872,14 @@ pub const Elf_MIPS_ABIFlags_v0 = extern struct {
 };
 
 comptime {
-    debug.assert(@sizeOf(Elf32_Ehdr) == 52);
-    debug.assert(@sizeOf(Elf64_Ehdr) == 64);
+    assert(@sizeOf(Elf32_Ehdr) == 52);
+    assert(@sizeOf(Elf64_Ehdr) == 64);
 
-    debug.assert(@sizeOf(Elf32_Phdr) == 32);
-    debug.assert(@sizeOf(Elf64_Phdr) == 56);
+    assert(@sizeOf(Elf32_Phdr) == 32);
+    assert(@sizeOf(Elf64_Phdr) == 56);
 
-    debug.assert(@sizeOf(Elf32_Shdr) == 40);
-    debug.assert(@sizeOf(Elf64_Shdr) == 64);
+    assert(@sizeOf(Elf32_Shdr) == 40);
+    assert(@sizeOf(Elf64_Shdr) == 64);
 }
 
 pub const Auxv = switch (@sizeOf(usize)) {
src/arch/x86_64/CodeGen.zig
@@ -6524,13 +6524,13 @@ fn airCmpxchg(self: *Self, inst: Air.Inst.Index) !void {
     const extra = self.air.extraData(Air.Block, ty_pl.payload);
     _ = ty_pl;
     _ = 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 });
 }
 
 fn airAtomicRmw(self: *Self, inst: Air.Inst.Index) !void {
     _ = 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 {
src/codegen/llvm.zig
@@ -1860,7 +1860,7 @@ pub const Object = struct {
                 var offset: u64 = 0;
 
                 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_align = field.alignment(target, layout);
@@ -2764,7 +2764,7 @@ pub const DeclGen = struct {
                 var any_underaligned_fields = false;
 
                 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_ty_align = field.ty.abiAlignment(target);
@@ -3443,7 +3443,7 @@ pub const DeclGen = struct {
                 var need_unnamed = false;
 
                 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);
                     big_align = @maximum(big_align, field_align);
@@ -9477,7 +9477,7 @@ fn llvmFieldIndex(
 
     var llvm_field_index: c_uint = 0;
     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);
         big_align = @maximum(big_align, field_align);
src/Module.zig
@@ -1368,18 +1368,20 @@ pub const Union = struct {
             }
         }
         payload_align = @maximum(payload_align, 1);
-        if (!have_tag or fields.len <= 1) return .{
-            .abi_size = std.mem.alignForwardGeneric(u64, payload_size, payload_align),
-            .abi_align = payload_align,
-            .most_aligned_field = most_aligned_field,
-            .most_aligned_field_size = most_aligned_field_size,
-            .biggest_field = biggest_field,
-            .payload_size = payload_size,
-            .payload_align = payload_align,
-            .tag_align = 0,
-            .tag_size = 0,
-            .padding = 0,
-        };
+        if (!have_tag or !u.tag_ty.hasRuntimeBits()) {
+            return .{
+                .abi_size = std.mem.alignForwardGeneric(u64, payload_size, payload_align),
+                .abi_align = payload_align,
+                .most_aligned_field = most_aligned_field,
+                .most_aligned_field_size = most_aligned_field_size,
+                .biggest_field = biggest_field,
+                .payload_size = payload_size,
+                .payload_align = payload_align,
+                .tag_align = 0,
+                .tag_size = 0,
+                .padding = 0,
+            };
+        }
         // Put the tag before or after the payload depending on which one's
         // alignment is greater.
         const tag_size = u.tag_ty.abiSize(target);
src/Sema.zig
@@ -28844,6 +28844,10 @@ pub fn typeHasOnePossibleValue(
         .enum_numbered => {
             const resolved_ty = try sema.resolveTypeFields(block, src, ty);
             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.values.count() == 0) {
                     return Value.zero; // auto-numbered
@@ -28857,6 +28861,9 @@ pub fn typeHasOnePossibleValue(
         .enum_full => {
             const resolved_ty = try sema.resolveTypeFields(block, src, ty);
             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.values.count() == 0) {
                     return Value.zero; // auto-numbered
src/type.zig
@@ -2310,6 +2310,8 @@ pub const Type = extern union {
     ///     fields will count towards the ABI size. For example, `struct {T: type, x: i32}`
     ///     hasRuntimeBits()=true and abiSize()=4
     /// * 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
     /// may return false positives.
     pub fn hasRuntimeBitsAdvanced(
@@ -2452,9 +2454,9 @@ pub const Type = extern union {
                     _ = try sk.sema.resolveTypeFields(sk.block, sk.src, ty);
                 }
                 assert(struct_obj.haveFieldTypes());
-                for (struct_obj.fields.values()) |value| {
-                    if (value.is_comptime) continue;
-                    if (try value.ty.hasRuntimeBitsAdvanced(ignore_comptime_only, sema_kit))
+                for (struct_obj.fields.values()) |field| {
+                    if (field.is_comptime) continue;
+                    if (try field.ty.hasRuntimeBitsAdvanced(ignore_comptime_only, sema_kit))
                         return true;
                 } else {
                     return false;
@@ -2463,7 +2465,7 @@ pub const Type = extern union {
 
             .enum_full => {
                 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 => {
                 const enum_simple = ty.castTag(.enum_simple).?.data;
@@ -2490,9 +2492,10 @@ pub const Type = extern union {
             },
             .union_safety_tagged, .union_tagged => {
                 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;
                 }
+
                 if (sema_kit) |sk| {
                     _ = 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) },
         };
         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;
@@ -4991,14 +4998,18 @@ pub const Type = extern union {
 
             .enum_numbered => {
                 const enum_numbered = ty.castTag(.enum_numbered).?.data;
-                if (enum_numbered.fields.count() == 1) {
-                    return enum_numbered.values.keys()[0];
-                } else {
+                // An explicit tag type is always provided for enum_numbered.
+                if (enum_numbered.tag_ty.hasRuntimeBits()) {
                     return null;
                 }
+                assert(enum_numbered.fields.count() == 1);
+                return enum_numbered.values.keys()[0];
             },
             .enum_full => {
                 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.values.count() == 0) {
                         return Value.zero;
@@ -5333,7 +5344,8 @@ pub const Type = extern union {
             .enum_numbered => return ty.castTag(.enum_numbered).?.data.tag_ty,
             .enum_simple => {
                 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.* = .{
                     .base = .{ .tag = .int_unsigned },
                     .data = bits,
@@ -5653,19 +5665,22 @@ pub const Type = extern union {
         target: Target,
 
         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;
 
-            const field = it.struct_obj.fields.values()[it.field];
-            defer it.field += 1;
-            if (!field.ty.hasRuntimeBits() or field.is_comptime)
-                return FieldOffset{ .field = it.field, .offset = it.offset };
+            const field = it.struct_obj.fields.values()[i];
+            it.field += 1;
+
+            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);
             it.big_align = @maximum(it.big_align, field_align);
-            it.offset = std.mem.alignForwardGeneric(u64, it.offset, field_align);
-            defer it.offset += field.ty.abiSize(it.target);
-            return FieldOffset{ .field = it.field, .offset = it.offset };
+            const field_offset = std.mem.alignForwardGeneric(u64, it.offset, field_align);
+            it.offset = field_offset + field.ty.abiSize(it.target);
+            return FieldOffset{ .field = i, .offset = field_offset };
         }
     };
 
test/behavior/bugs/1111.zig
@@ -1,11 +0,0 @@
-const Foo = enum(c_int) {
-    Bar = -1,
-};
-
-test "issue 1111 fixed" {
-    const v = Foo.Bar;
-
-    switch (v) {
-        Foo.Bar => return,
-    }
-}
test/behavior/enum.zig
@@ -1,6 +1,7 @@
 const builtin = @import("builtin");
 const std = @import("std");
 const expect = std.testing.expect;
+const assert = std.debug.assert;
 const mem = std.mem;
 const Tag = std.meta.Tag;
 
@@ -1128,3 +1129,44 @@ test "tag name functions are unique" {
         _ = 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,
+    }
+}
test/behavior/union.zig
@@ -1,6 +1,7 @@
 const builtin = @import("builtin");
 const std = @import("std");
 const expect = std.testing.expect;
+const assert = std.debug.assert;
 const expectEqual = std.testing.expectEqual;
 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_arm) 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 Type = enum(u8) { no_op = 105 };
@@ -1079,11 +1082,7 @@ test "@unionInit on union with tag but no fields" {
         };
 
         comptime {
-            if (builtin.zig_backend == .stage1) {
-                // stage1 gets the wrong answer here
-            } else {
-                std.debug.assert(@sizeOf(Data) == 0);
-            }
+            assert(@sizeOf(Data) == 1);
         }
 
         fn doTheTest() !void {
test/behavior.zig
@@ -26,7 +26,6 @@ test {
     _ = @import("behavior/bugs/920.zig");
     _ = @import("behavior/bugs/1025.zig");
     _ = @import("behavior/bugs/1076.zig");
-    _ = @import("behavior/bugs/1111.zig");
     _ = @import("behavior/bugs/1277.zig");
     _ = @import("behavior/bugs/1310.zig");
     _ = @import("behavior/bugs/1381.zig");