Commit cb616cb797

Andrew Kelley <andrew@ziglang.org>
2021-10-06 06:38:47
stage2: implement runtime `@intToEnum`
* Update AIR instruction `intcast` to allow the dest type to be an enum. * LLVM backend: update `intcast` to support when the bit counts of operand and dest type are the same. This was already a requirement of the instruction previously. * Type: `intInfo` supports the case when the type is an enum, and retrieves the info for the integer tag type. This makes it pretty easy for backends to implement `intcast` without having to care explicitly that the new type is an enum. As a bonus, simple enums never have to go through the type system; their signedness and bit count are computed directly. The "int to enum" behavior test case is now passing for stage2 in the LLVM backend.
1 parent 941b2f0
Changed files (6)
src/codegen/llvm.zig
@@ -2381,8 +2381,11 @@ pub const FuncGen = struct {
                 .signed => return self.builder.buildSExt(operand, dest_llvm_ty, ""),
                 .unsigned => return self.builder.buildZExt(operand, dest_llvm_ty, ""),
             }
+        } else if (operand_info.bits > dest_info.bits) {
+            return self.builder.buildTrunc(operand, dest_llvm_ty, "");
+        } else {
+            return operand;
         }
-        return self.builder.buildTrunc(operand, dest_llvm_ty, "");
     }
 
     fn airTrunc(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
src/Air.zig
@@ -272,8 +272,11 @@ pub const Inst = struct {
         /// Uses the `ty_op` field.
         fpext,
         /// Returns an integer with a different type than the operand. The new type may have
-        /// fewer, the same, or more bits than the operand type. However, the instruction
+        /// fewer, the same, or more bits than the operand type. The new type may also
+        /// differ in signedness from the operand type. However, the instruction
         /// guarantees that the same integer value fits in both types.
+        /// The new type may also be an enum type, in which case the integer cast operates on
+        /// the integer tag type of the enum.
         /// See `trunc` for integer truncation.
         /// Uses the `ty_op` field.
         intcast,
src/Sema.zig
@@ -4312,7 +4312,8 @@ fn zirIntToEnum(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!A
     }
 
     try sema.requireRuntimeBlock(block, src);
-    return block.addTyOp(.bitcast, dest_ty, operand);
+    // TODO insert safety check to make sure the value matches an enum value
+    return block.addTyOp(.intcast, dest_ty, operand);
 }
 
 /// Pointer in, pointer out.
@@ -5050,6 +5051,7 @@ fn zirIntCast(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air
     }
 
     try sema.requireRuntimeBlock(block, operand_src);
+    // TODO insert safety check to make sure the value fits in the dest type
     return block.addTyOp(.intcast, dest_type, operand);
 }
 
src/type.zig
@@ -2657,38 +2657,49 @@ pub const Type = extern union {
         };
     }
 
-    /// Asserts the type is an integer.
+    /// Asserts the type is an integer or enum.
     pub fn intInfo(self: Type, target: Target) struct { signedness: std.builtin.Signedness, bits: u16 } {
-        return switch (self.tag()) {
-            .int_unsigned => .{
+        var ty = self;
+        while (true) switch (ty.tag()) {
+            .int_unsigned => return .{
                 .signedness = .unsigned,
-                .bits = self.castTag(.int_unsigned).?.data,
+                .bits = ty.castTag(.int_unsigned).?.data,
             },
-            .int_signed => .{
+            .int_signed => return .{
                 .signedness = .signed,
-                .bits = self.castTag(.int_signed).?.data,
-            },
-            .u1 => .{ .signedness = .unsigned, .bits = 1 },
-            .u8 => .{ .signedness = .unsigned, .bits = 8 },
-            .i8 => .{ .signedness = .signed, .bits = 8 },
-            .u16 => .{ .signedness = .unsigned, .bits = 16 },
-            .i16 => .{ .signedness = .signed, .bits = 16 },
-            .u32 => .{ .signedness = .unsigned, .bits = 32 },
-            .i32 => .{ .signedness = .signed, .bits = 32 },
-            .u64 => .{ .signedness = .unsigned, .bits = 64 },
-            .i64 => .{ .signedness = .signed, .bits = 64 },
-            .u128 => .{ .signedness = .unsigned, .bits = 128 },
-            .i128 => .{ .signedness = .signed, .bits = 128 },
-            .usize => .{ .signedness = .unsigned, .bits = target.cpu.arch.ptrBitWidth() },
-            .isize => .{ .signedness = .signed, .bits = target.cpu.arch.ptrBitWidth() },
-            .c_short => .{ .signedness = .signed, .bits = CType.short.sizeInBits(target) },
-            .c_ushort => .{ .signedness = .unsigned, .bits = CType.ushort.sizeInBits(target) },
-            .c_int => .{ .signedness = .signed, .bits = CType.int.sizeInBits(target) },
-            .c_uint => .{ .signedness = .unsigned, .bits = CType.uint.sizeInBits(target) },
-            .c_long => .{ .signedness = .signed, .bits = CType.long.sizeInBits(target) },
-            .c_ulong => .{ .signedness = .unsigned, .bits = CType.ulong.sizeInBits(target) },
-            .c_longlong => .{ .signedness = .signed, .bits = CType.longlong.sizeInBits(target) },
-            .c_ulonglong => .{ .signedness = .unsigned, .bits = CType.ulonglong.sizeInBits(target) },
+                .bits = ty.castTag(.int_signed).?.data,
+            },
+            .u1 => return .{ .signedness = .unsigned, .bits = 1 },
+            .u8 => return .{ .signedness = .unsigned, .bits = 8 },
+            .i8 => return .{ .signedness = .signed, .bits = 8 },
+            .u16 => return .{ .signedness = .unsigned, .bits = 16 },
+            .i16 => return .{ .signedness = .signed, .bits = 16 },
+            .u32 => return .{ .signedness = .unsigned, .bits = 32 },
+            .i32 => return .{ .signedness = .signed, .bits = 32 },
+            .u64 => return .{ .signedness = .unsigned, .bits = 64 },
+            .i64 => return .{ .signedness = .signed, .bits = 64 },
+            .u128 => return .{ .signedness = .unsigned, .bits = 128 },
+            .i128 => return .{ .signedness = .signed, .bits = 128 },
+            .usize => return .{ .signedness = .unsigned, .bits = target.cpu.arch.ptrBitWidth() },
+            .isize => return .{ .signedness = .signed, .bits = target.cpu.arch.ptrBitWidth() },
+            .c_short => return .{ .signedness = .signed, .bits = CType.short.sizeInBits(target) },
+            .c_ushort => return .{ .signedness = .unsigned, .bits = CType.ushort.sizeInBits(target) },
+            .c_int => return .{ .signedness = .signed, .bits = CType.int.sizeInBits(target) },
+            .c_uint => return .{ .signedness = .unsigned, .bits = CType.uint.sizeInBits(target) },
+            .c_long => return .{ .signedness = .signed, .bits = CType.long.sizeInBits(target) },
+            .c_ulong => return .{ .signedness = .unsigned, .bits = CType.ulong.sizeInBits(target) },
+            .c_longlong => return .{ .signedness = .signed, .bits = CType.longlong.sizeInBits(target) },
+            .c_ulonglong => return .{ .signedness = .unsigned, .bits = CType.ulonglong.sizeInBits(target) },
+
+            .enum_full, .enum_nonexhaustive => ty = ty.cast(Payload.EnumFull).?.data.tag_ty,
+            .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()),
+                };
+            },
 
             else => unreachable,
         };
@@ -3905,20 +3916,22 @@ pub const Type = extern union {
         return Type.initPayload(&type_payload.base);
     }
 
+    pub fn smallestUnsignedBits(max: u64) u16 {
+        if (max == 0) return 0;
+        const base = std.math.log2(max);
+        const upper = (@as(u64, 1) << @intCast(u6, base)) - 1;
+        return @intCast(u16, base + @boolToInt(upper < max));
+    }
+
     pub fn smallestUnsignedInt(arena: *Allocator, max: u64) !Type {
-        const bits = bits: {
-            if (max == 0) break :bits 0;
-            const base = std.math.log2(max);
-            const upper = (@as(u64, 1) << @intCast(u6, base)) - 1;
-            break :bits base + @boolToInt(upper < max);
-        };
-        return switch (@intCast(u16, bits)) {
+        const bits = smallestUnsignedBits(max);
+        return switch (bits) {
             1 => initTag(.u1),
             8 => initTag(.u8),
             16 => initTag(.u16),
             32 => initTag(.u32),
             64 => initTag(.u64),
-            else => |b| return Tag.int_unsigned.create(arena, b),
+            else => return Tag.int_unsigned.create(arena, bits),
         };
     }
 };
test/behavior/enum.zig
@@ -17,6 +17,15 @@ test "enum to int" {
     try shouldEqual(Number.Four, 4);
 }
 
+fn testIntToEnumEval(x: i32) !void {
+    try expect(@intToEnum(IntToEnumNumber, x) == IntToEnumNumber.Three);
+}
+const IntToEnumNumber = enum { Zero, One, Two, Three, Four };
+
+test "int to enum" {
+    try testIntToEnumEval(3);
+}
+
 const ValueCount1 = enum {
     I0,
 };
test/behavior/enum_stage1.zig
@@ -104,18 +104,6 @@ const Bar = enum { A, B, C, D };
 
 const Number = enum { Zero, One, Two, Three, Four };
 
-fn shouldEqual(n: Number, expected: u3) !void {
-    try expect(@enumToInt(n) == expected);
-}
-
-test "int to enum" {
-    try testIntToEnumEval(3);
-}
-fn testIntToEnumEval(x: i32) !void {
-    try expect(@intToEnum(IntToEnumNumber, x) == IntToEnumNumber.Three);
-}
-const IntToEnumNumber = enum { Zero, One, Two, Three, Four };
-
 test "@tagName" {
     try expect(mem.eql(u8, testEnumTagNameBare(BareNumber.Three), "Three"));
     comptime try expect(mem.eql(u8, testEnumTagNameBare(BareNumber.Three), "Three"));
@@ -131,544 +119,9 @@ fn testEnumTagNameBare(n: anytype) []const u8 {
 }
 
 const BareNumber = enum { One, Two, Three };
-
 const NonExhaustive = enum(u8) { A, B, _ };
-
-const ValueCount1 = enum {
-    I0,
-};
-const ValueCount2 = enum {
-    I0,
-    I1,
-};
-const ValueCount256 = enum {
-    I0,
-    I1,
-    I2,
-    I3,
-    I4,
-    I5,
-    I6,
-    I7,
-    I8,
-    I9,
-    I10,
-    I11,
-    I12,
-    I13,
-    I14,
-    I15,
-    I16,
-    I17,
-    I18,
-    I19,
-    I20,
-    I21,
-    I22,
-    I23,
-    I24,
-    I25,
-    I26,
-    I27,
-    I28,
-    I29,
-    I30,
-    I31,
-    I32,
-    I33,
-    I34,
-    I35,
-    I36,
-    I37,
-    I38,
-    I39,
-    I40,
-    I41,
-    I42,
-    I43,
-    I44,
-    I45,
-    I46,
-    I47,
-    I48,
-    I49,
-    I50,
-    I51,
-    I52,
-    I53,
-    I54,
-    I55,
-    I56,
-    I57,
-    I58,
-    I59,
-    I60,
-    I61,
-    I62,
-    I63,
-    I64,
-    I65,
-    I66,
-    I67,
-    I68,
-    I69,
-    I70,
-    I71,
-    I72,
-    I73,
-    I74,
-    I75,
-    I76,
-    I77,
-    I78,
-    I79,
-    I80,
-    I81,
-    I82,
-    I83,
-    I84,
-    I85,
-    I86,
-    I87,
-    I88,
-    I89,
-    I90,
-    I91,
-    I92,
-    I93,
-    I94,
-    I95,
-    I96,
-    I97,
-    I98,
-    I99,
-    I100,
-    I101,
-    I102,
-    I103,
-    I104,
-    I105,
-    I106,
-    I107,
-    I108,
-    I109,
-    I110,
-    I111,
-    I112,
-    I113,
-    I114,
-    I115,
-    I116,
-    I117,
-    I118,
-    I119,
-    I120,
-    I121,
-    I122,
-    I123,
-    I124,
-    I125,
-    I126,
-    I127,
-    I128,
-    I129,
-    I130,
-    I131,
-    I132,
-    I133,
-    I134,
-    I135,
-    I136,
-    I137,
-    I138,
-    I139,
-    I140,
-    I141,
-    I142,
-    I143,
-    I144,
-    I145,
-    I146,
-    I147,
-    I148,
-    I149,
-    I150,
-    I151,
-    I152,
-    I153,
-    I154,
-    I155,
-    I156,
-    I157,
-    I158,
-    I159,
-    I160,
-    I161,
-    I162,
-    I163,
-    I164,
-    I165,
-    I166,
-    I167,
-    I168,
-    I169,
-    I170,
-    I171,
-    I172,
-    I173,
-    I174,
-    I175,
-    I176,
-    I177,
-    I178,
-    I179,
-    I180,
-    I181,
-    I182,
-    I183,
-    I184,
-    I185,
-    I186,
-    I187,
-    I188,
-    I189,
-    I190,
-    I191,
-    I192,
-    I193,
-    I194,
-    I195,
-    I196,
-    I197,
-    I198,
-    I199,
-    I200,
-    I201,
-    I202,
-    I203,
-    I204,
-    I205,
-    I206,
-    I207,
-    I208,
-    I209,
-    I210,
-    I211,
-    I212,
-    I213,
-    I214,
-    I215,
-    I216,
-    I217,
-    I218,
-    I219,
-    I220,
-    I221,
-    I222,
-    I223,
-    I224,
-    I225,
-    I226,
-    I227,
-    I228,
-    I229,
-    I230,
-    I231,
-    I232,
-    I233,
-    I234,
-    I235,
-    I236,
-    I237,
-    I238,
-    I239,
-    I240,
-    I241,
-    I242,
-    I243,
-    I244,
-    I245,
-    I246,
-    I247,
-    I248,
-    I249,
-    I250,
-    I251,
-    I252,
-    I253,
-    I254,
-    I255,
-};
-const ValueCount257 = enum {
-    I0,
-    I1,
-    I2,
-    I3,
-    I4,
-    I5,
-    I6,
-    I7,
-    I8,
-    I9,
-    I10,
-    I11,
-    I12,
-    I13,
-    I14,
-    I15,
-    I16,
-    I17,
-    I18,
-    I19,
-    I20,
-    I21,
-    I22,
-    I23,
-    I24,
-    I25,
-    I26,
-    I27,
-    I28,
-    I29,
-    I30,
-    I31,
-    I32,
-    I33,
-    I34,
-    I35,
-    I36,
-    I37,
-    I38,
-    I39,
-    I40,
-    I41,
-    I42,
-    I43,
-    I44,
-    I45,
-    I46,
-    I47,
-    I48,
-    I49,
-    I50,
-    I51,
-    I52,
-    I53,
-    I54,
-    I55,
-    I56,
-    I57,
-    I58,
-    I59,
-    I60,
-    I61,
-    I62,
-    I63,
-    I64,
-    I65,
-    I66,
-    I67,
-    I68,
-    I69,
-    I70,
-    I71,
-    I72,
-    I73,
-    I74,
-    I75,
-    I76,
-    I77,
-    I78,
-    I79,
-    I80,
-    I81,
-    I82,
-    I83,
-    I84,
-    I85,
-    I86,
-    I87,
-    I88,
-    I89,
-    I90,
-    I91,
-    I92,
-    I93,
-    I94,
-    I95,
-    I96,
-    I97,
-    I98,
-    I99,
-    I100,
-    I101,
-    I102,
-    I103,
-    I104,
-    I105,
-    I106,
-    I107,
-    I108,
-    I109,
-    I110,
-    I111,
-    I112,
-    I113,
-    I114,
-    I115,
-    I116,
-    I117,
-    I118,
-    I119,
-    I120,
-    I121,
-    I122,
-    I123,
-    I124,
-    I125,
-    I126,
-    I127,
-    I128,
-    I129,
-    I130,
-    I131,
-    I132,
-    I133,
-    I134,
-    I135,
-    I136,
-    I137,
-    I138,
-    I139,
-    I140,
-    I141,
-    I142,
-    I143,
-    I144,
-    I145,
-    I146,
-    I147,
-    I148,
-    I149,
-    I150,
-    I151,
-    I152,
-    I153,
-    I154,
-    I155,
-    I156,
-    I157,
-    I158,
-    I159,
-    I160,
-    I161,
-    I162,
-    I163,
-    I164,
-    I165,
-    I166,
-    I167,
-    I168,
-    I169,
-    I170,
-    I171,
-    I172,
-    I173,
-    I174,
-    I175,
-    I176,
-    I177,
-    I178,
-    I179,
-    I180,
-    I181,
-    I182,
-    I183,
-    I184,
-    I185,
-    I186,
-    I187,
-    I188,
-    I189,
-    I190,
-    I191,
-    I192,
-    I193,
-    I194,
-    I195,
-    I196,
-    I197,
-    I198,
-    I199,
-    I200,
-    I201,
-    I202,
-    I203,
-    I204,
-    I205,
-    I206,
-    I207,
-    I208,
-    I209,
-    I210,
-    I211,
-    I212,
-    I213,
-    I214,
-    I215,
-    I216,
-    I217,
-    I218,
-    I219,
-    I220,
-    I221,
-    I222,
-    I223,
-    I224,
-    I225,
-    I226,
-    I227,
-    I228,
-    I229,
-    I230,
-    I231,
-    I232,
-    I233,
-    I234,
-    I235,
-    I236,
-    I237,
-    I238,
-    I239,
-    I240,
-    I241,
-    I242,
-    I243,
-    I244,
-    I245,
-    I246,
-    I247,
-    I248,
-    I249,
-    I250,
-    I251,
-    I252,
-    I253,
-    I254,
-    I255,
-    I256,
-};
-
-const Small2 = enum(u2) {
-    One,
-    Two,
-};
-const Small = enum(u2) {
-    One,
-    Two,
-    Three,
-    Four,
-};
+const Small2 = enum(u2) { One, Two };
+const Small = enum(u2) { One, Two, Three, Four };
 
 test "set enum tag type" {
     {