Commit e16ddad49f

Andrew Kelley <andrew@ziglang.org>
2021-10-06 08:05:14
stage2: enum fixes
* Sema: fix a missing copy on enum tag values * LLVM backend: fix lowering of enum constant values for enums with specified tag values. * Value: fix enumToInt for `enum_numbered` cases. The float widening behavior tests which rely on compiler-rt symbols are now passing.
1 parent cb616cb
src/codegen/llvm.zig
@@ -889,9 +889,9 @@ pub const DeclGen = struct {
             .Int => {
                 var bigint_space: Value.BigIntSpace = undefined;
                 const bigint = tv.val.toBigInt(&bigint_space);
-
-                const llvm_type = try self.llvmType(tv.ty);
-                if (bigint.eqZero()) return llvm_type.constNull();
+                const target = self.module.getTarget();
+                const int_info = tv.ty.intInfo(target);
+                const llvm_type = self.context.intType(int_info.bits);
 
                 const unsigned_val = if (bigint.limbs.len == 1)
                     llvm_type.constInt(bigint.limbs[0], .False)
@@ -903,15 +903,24 @@ pub const DeclGen = struct {
                 return unsigned_val;
             },
             .Enum => {
-                const llvm_type = try self.llvmType(tv.ty);
-                const uint: u64 = uint: {
-                    if (tv.val.castTag(.enum_field_index)) |payload| {
-                        break :uint payload.data;
-                    }
-                    break :uint tv.val.toUnsignedInt();
-                };
-                const llvm_int = llvm_type.constInt(uint, .False);
-                return llvm_int;
+                var int_buffer: Value.Payload.U64 = undefined;
+                const int_val = tv.enumToInt(&int_buffer);
+
+                var bigint_space: Value.BigIntSpace = undefined;
+                const bigint = int_val.toBigInt(&bigint_space);
+
+                const target = self.module.getTarget();
+                const int_info = tv.ty.intInfo(target);
+                const llvm_type = self.context.intType(int_info.bits);
+
+                const unsigned_val = if (bigint.limbs.len == 1)
+                    llvm_type.constInt(bigint.limbs[0], .False)
+                else
+                    llvm_type.constIntOfArbitraryPrecision(@intCast(c_uint, bigint.limbs.len), bigint.limbs.ptr);
+                if (!bigint.positive) {
+                    return llvm.constNeg(unsigned_val);
+                }
+                return unsigned_val;
             },
             .Float => {
                 const llvm_ty = try self.llvmType(tv.ty);
src/Sema.zig
@@ -1644,7 +1644,10 @@ fn zirEnumDecl(
             // that points to this default value expression rather than the struct.
             // But only resolve the source location if we need to emit a compile error.
             const tag_val = (try sema.resolveInstConst(block, src, tag_val_ref)).val;
-            enum_obj.values.putAssumeCapacityNoClobberContext(tag_val, {}, .{ .ty = enum_obj.tag_ty });
+            const copied_tag_val = try tag_val.copy(&new_decl_arena.allocator);
+            enum_obj.values.putAssumeCapacityNoClobberContext(copied_tag_val, {}, .{
+                .ty = enum_obj.tag_ty,
+            });
         } else if (any_values) {
             const tag_val = try Value.Tag.int_u64.create(&new_decl_arena.allocator, field_i);
             enum_obj.values.putAssumeCapacityNoClobberContext(tag_val, {}, .{ .ty = enum_obj.tag_ty });
src/type.zig
@@ -2695,10 +2695,9 @@ pub const Type = extern union {
             .enum_numbered => ty = self.castTag(.enum_numbered).?.data.tag_ty,
             .enum_simple => {
                 const enum_obj = self.castTag(.enum_simple).?.data;
-                return .{
-                    .signedness = .unsigned,
-                    .bits = smallestUnsignedBits(enum_obj.fields.count()),
-                };
+                const field_count = enum_obj.fields.count();
+                if (field_count == 0) return .{ .signedness = .unsigned, .bits = 0 };
+                return .{ .signedness = .unsigned, .bits = smallestUnsignedBits(field_count - 1) };
             },
 
             else => unreachable,
src/TypedValue.zig
@@ -38,3 +38,7 @@ pub fn eql(a: TypedValue, b: TypedValue) bool {
 pub fn hash(tv: TypedValue, hasher: *std.hash.Wyhash) void {
     return tv.val.hash(tv.ty, hasher);
 }
+
+pub fn enumToInt(tv: TypedValue, buffer: *Value.Payload.U64) Value {
+    return tv.val.enumToInt(tv.ty, buffer);
+}
src/value.zig
@@ -858,6 +858,19 @@ pub const Value = extern union {
                         return Value.initPayload(&buffer.base);
                     }
                 },
+                .enum_numbered => {
+                    const enum_obj = ty.castTag(.enum_numbered).?.data;
+                    if (enum_obj.values.count() != 0) {
+                        return enum_obj.values.keys()[field_index];
+                    } else {
+                        // Field index and integer values are the same.
+                        buffer.* = .{
+                            .base = .{ .tag = .int_u64 },
+                            .data = field_index,
+                        };
+                        return Value.initPayload(&buffer.base);
+                    }
+                },
                 .enum_simple => {
                     // Field index and integer values are the same.
                     buffer.* = .{
test/behavior/enum_stage1.zig
@@ -2,6 +2,28 @@ const expect = @import("std").testing.expect;
 const mem = @import("std").mem;
 const Tag = @import("std").meta.Tag;
 
+const MultipleChoice = enum(u32) {
+    A = 20,
+    B = 40,
+    C = 60,
+    D = 1000,
+};
+
+fn testEnumWithSpecifiedTagValues(x: MultipleChoice) !void {
+    try expect(@enumToInt(x) == 60);
+    try expect(1234 == switch (x) {
+        MultipleChoice.A => 1,
+        MultipleChoice.B => 2,
+        MultipleChoice.C => @as(u32, 1234),
+        MultipleChoice.D => 4,
+    });
+}
+
+test "enum with specified tag values" {
+    try testEnumWithSpecifiedTagValues(MultipleChoice.C);
+    comptime try testEnumWithSpecifiedTagValues(MultipleChoice.C);
+}
+
 test "non-exhaustive enum" {
     const S = struct {
         const E = enum(u8) {
@@ -188,28 +210,6 @@ fn testCastEnumTag(value: Small2) !void {
     try expect(@enumToInt(value) == 1);
 }
 
-const MultipleChoice = enum(u32) {
-    A = 20,
-    B = 40,
-    C = 60,
-    D = 1000,
-};
-
-test "enum with specified tag values" {
-    try testEnumWithSpecifiedTagValues(MultipleChoice.C);
-    comptime try testEnumWithSpecifiedTagValues(MultipleChoice.C);
-}
-
-fn testEnumWithSpecifiedTagValues(x: MultipleChoice) !void {
-    try expect(@enumToInt(x) == 60);
-    try expect(1234 == switch (x) {
-        MultipleChoice.A => 1,
-        MultipleChoice.B => 2,
-        MultipleChoice.C => @as(u32, 1234),
-        MultipleChoice.D => 4,
-    });
-}
-
 const MultipleChoice2 = enum(u32) {
     Unspecified1,
     A = 20,
test/behavior/widening.zig
@@ -19,12 +19,6 @@ test "implicit unsigned integer to signed integer" {
 }
 
 test "float widening" {
-    if (@import("builtin").zig_is_stage2) {
-        // This test is passing but it depends on compiler-rt symbols, which
-        // cannot yet be built with stage2 due to
-        // "TODO implement equality comparison between a union's tag value and an enum literal"
-        return error.SkipZigTest;
-    }
     var a: f16 = 12.34;
     var b: f32 = a;
     var c: f64 = b;
@@ -35,12 +29,6 @@ test "float widening" {
 }
 
 test "float widening f16 to f128" {
-    if (@import("builtin").zig_is_stage2) {
-        // This test is passing but it depends on compiler-rt symbols, which
-        // cannot yet be built with stage2 due to
-        // "TODO implement equality comparison between a union's tag value and an enum literal"
-        return error.SkipZigTest;
-    }
     // TODO https://github.com/ziglang/zig/issues/3282
     if (@import("builtin").stage2_arch == .aarch64) return error.SkipZigTest;
     if (@import("builtin").stage2_arch == .powerpc64le) return error.SkipZigTest;