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.
This commit is contained in:
Andrew Kelley 2021-10-05 21:38:47 -07:00
parent 941b2f0d5e
commit cb616cb797
6 changed files with 70 additions and 587 deletions

View File

@ -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,

View File

@ -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);
}

View File

@ -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 {

View File

@ -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,
.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()),
};
},
.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) },
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),
};
}
};

View File

@ -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,
};

View File

@ -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" {
{