From 2cdbb5f47260d2510ce478c77fa9c3c7c29fb671 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 21 Apr 2020 19:48:59 -0400 Subject: [PATCH] ir: analyze int casting --- lib/std/math/big/int.zig | 2 +- lib/std/target.zig | 2 +- src-self-hosted/c_int.zig | 169 ------------------------------------ src-self-hosted/ir.zig | 49 +++++++++++ src-self-hosted/ir/text.zig | 14 +++ src-self-hosted/type.zig | 162 ++++++++++++++++++++++++++++++++++ src-self-hosted/value.zig | 73 ++++++++++++++++ src/ir.cpp | 6 +- 8 files changed, 303 insertions(+), 174 deletions(-) delete mode 100644 src-self-hosted/c_int.zig diff --git a/lib/std/math/big/int.zig b/lib/std/math/big/int.zig index 01f4b6dccd..ac0eed3ebc 100644 --- a/lib/std/math/big/int.zig +++ b/lib/std/math/big/int.zig @@ -237,7 +237,7 @@ pub const Int = struct { return bits; } - fn fitsInTwosComp(self: Int, is_signed: bool, bit_count: usize) bool { + pub fn fitsInTwosComp(self: Int, is_signed: bool, bit_count: usize) bool { if (self.eqZero()) { return true; } diff --git a/lib/std/target.zig b/lib/std/target.zig index 8388fda72a..eb4898e6ab 100644 --- a/lib/std/target.zig +++ b/lib/std/target.zig @@ -761,7 +761,7 @@ pub const Target = struct { }; } - pub fn ptrBitWidth(arch: Arch) u32 { + pub fn ptrBitWidth(arch: Arch) u16 { switch (arch) { .avr, .msp430, diff --git a/src-self-hosted/c_int.zig b/src-self-hosted/c_int.zig deleted file mode 100644 index e61a5bf657..0000000000 --- a/src-self-hosted/c_int.zig +++ /dev/null @@ -1,169 +0,0 @@ -const Target = @import("std").Target; - -pub const CInt = struct { - id: Id, - zig_name: []const u8, - c_name: []const u8, - is_signed: bool, - - pub const Id = enum { - Short, - UShort, - Int, - UInt, - Long, - ULong, - LongLong, - ULongLong, - }; - - pub const list = [_]CInt{ - CInt{ - .id = .Short, - .zig_name = "c_short", - .c_name = "short", - .is_signed = true, - }, - CInt{ - .id = .UShort, - .zig_name = "c_ushort", - .c_name = "unsigned short", - .is_signed = false, - }, - CInt{ - .id = .Int, - .zig_name = "c_int", - .c_name = "int", - .is_signed = true, - }, - CInt{ - .id = .UInt, - .zig_name = "c_uint", - .c_name = "unsigned int", - .is_signed = false, - }, - CInt{ - .id = .Long, - .zig_name = "c_long", - .c_name = "long", - .is_signed = true, - }, - CInt{ - .id = .ULong, - .zig_name = "c_ulong", - .c_name = "unsigned long", - .is_signed = false, - }, - CInt{ - .id = .LongLong, - .zig_name = "c_longlong", - .c_name = "long long", - .is_signed = true, - }, - CInt{ - .id = .ULongLong, - .zig_name = "c_ulonglong", - .c_name = "unsigned long long", - .is_signed = false, - }, - }; - - pub fn sizeInBits(cint: CInt, self: Target) u32 { - const arch = self.cpu.arch; - switch (self.os.tag) { - .freestanding, .other => switch (self.cpu.arch) { - .msp430 => switch (cint.id) { - .Short, - .UShort, - .Int, - .UInt, - => return 16, - .Long, - .ULong, - => return 32, - .LongLong, - .ULongLong, - => return 64, - }, - else => switch (cint.id) { - .Short, - .UShort, - => return 16, - .Int, - .UInt, - => return 32, - .Long, - .ULong, - => return self.cpu.arch.ptrBitWidth(), - .LongLong, - .ULongLong, - => return 64, - }, - }, - - .linux, - .macosx, - .freebsd, - .openbsd, - => switch (cint.id) { - .Short, - .UShort, - => return 16, - .Int, - .UInt, - => return 32, - .Long, - .ULong, - => return self.cpu.arch.ptrBitWidth(), - .LongLong, - .ULongLong, - => return 64, - }, - - .windows, .uefi => switch (cint.id) { - .Short, - .UShort, - => return 16, - .Int, - .UInt, - => return 32, - .Long, - .ULong, - .LongLong, - .ULongLong, - => return 64, - }, - - .ananas, - .cloudabi, - .dragonfly, - .fuchsia, - .ios, - .kfreebsd, - .lv2, - .netbsd, - .solaris, - .haiku, - .minix, - .rtems, - .nacl, - .cnk, - .aix, - .cuda, - .nvcl, - .amdhsa, - .ps4, - .elfiamcu, - .tvos, - .watchos, - .mesa3d, - .contiki, - .amdpal, - .hermit, - .hurd, - .wasi, - .emscripten, - => @panic("TODO specify the C integer type sizes for this OS"), - } - } -}; diff --git a/src-self-hosted/ir.zig b/src-self-hosted/ir.zig index 01c7872019..eeba732f6d 100644 --- a/src-self-hosted/ir.zig +++ b/src-self-hosted/ir.zig @@ -6,6 +6,7 @@ const Type = @import("type.zig").Type; const assert = std.debug.assert; const text = @import("ir/text.zig"); const BigInt = std.math.big.Int; +const Target = std.Target; /// These are in-memory, analyzed instructions. See `text.Inst` for the representation /// of instructions that correspond to the ZIR text format. @@ -99,6 +100,8 @@ pub const ErrorMsg = struct { }; pub fn analyze(allocator: *Allocator, old_module: text.Module) !Module { + const native_info = try std.zig.system.NativeTargetInfo.detect(allocator, .{}); + var ctx = Analyze{ .allocator = allocator, .arena = std.heap.ArenaAllocator.init(allocator), @@ -107,6 +110,7 @@ pub fn analyze(allocator: *Allocator, old_module: text.Module) !Module { .decl_table = std.AutoHashMap(*text.Inst, Analyze.NewDecl).init(allocator), .exports = std.ArrayList(Module.Export).init(allocator), .fns = std.ArrayList(Module.Fn).init(allocator), + .target = native_info.target, }; defer ctx.errors.deinit(); defer ctx.decl_table.deinit(); @@ -135,6 +139,7 @@ const Analyze = struct { decl_table: std.AutoHashMap(*text.Inst, NewDecl), exports: std.ArrayList(Module.Export), fns: std.ArrayList(Module.Fn), + target: Target, const NewDecl = struct { /// null means a semantic analysis error happened @@ -336,6 +341,7 @@ const Analyze = struct { .@"export" => return self.fail(old_inst.src, "TODO implement analyzing {}", .{@tagName(old_inst.tag)}), .primitive => return self.analyzeInstPrimitive(func, old_inst.cast(text.Inst.Primitive).?), .fntype => return self.analyzeInstFnType(func, old_inst.cast(text.Inst.FnType).?), + .intcast => return self.analyzeInstIntCast(func, old_inst.cast(text.Inst.IntCast).?), } } @@ -402,6 +408,38 @@ const Analyze = struct { return self.coerce(dest_type, new_inst); } + fn analyzeInstIntCast(self: *Analyze, func: ?*Fn, intcast: *text.Inst.IntCast) InnerError!*Inst { + const dest_type = try self.resolveType(func, intcast.positionals.dest_type); + const new_inst = try self.resolveInst(func, intcast.positionals.value); + + const dest_is_comptime_int = switch (dest_type.zigTypeTag()) { + .ComptimeInt => true, + .Int => false, + else => return self.fail( + intcast.positionals.dest_type.src, + "expected integer type, found '{}'", + .{ + dest_type, + }, + ), + }; + + switch (new_inst.ty.zigTypeTag()) { + .ComptimeInt, .Int => {}, + else => return self.fail( + intcast.positionals.value.src, + "expected integer type, found '{}'", + .{new_inst.ty}, + ), + } + + if (dest_is_comptime_int or new_inst.value() != null) { + return self.coerce(dest_type, new_inst); + } + + return self.fail(intcast.base.src, "TODO implement analyze widen or shorten int", .{}); + } + fn coerce(self: *Analyze, dest_type: Type, inst: *Inst) !*Inst { const in_memory_result = coerceInMemoryAllowed(dest_type, inst.ty); if (in_memory_result == .ok) { @@ -420,6 +458,17 @@ const Analyze = struct { return self.coerceArrayPtrToSlice(dest_type, inst); } } + + // comptime_int to fixed-width integer + if (inst.ty.zigTypeTag() == .ComptimeInt and dest_type.zigTypeTag() == .Int) { + // The representation is already correct; we only need to make sure it fits in the destination type. + const val = inst.value().?; // comptime_int always has comptime known value + if (!val.intFitsInType(dest_type, self.target)) { + return self.fail(inst.src, "type {} cannot represent integer value {}", .{ inst.ty, val }); + } + return self.constInst(inst.src, .{ .ty = dest_type, .val = val }); + } + return self.fail(inst.src, "TODO implement type coercion", .{}); } diff --git a/src-self-hosted/ir/text.zig b/src-self-hosted/ir/text.zig index cf63ee2a17..5a2181f882 100644 --- a/src-self-hosted/ir/text.zig +++ b/src-self-hosted/ir/text.zig @@ -28,6 +28,7 @@ pub const Inst = struct { @"export", primitive, fntype, + intcast, }; pub fn TagToType(tag: Tag) type { @@ -44,6 +45,7 @@ pub const Inst = struct { .@"export" => Export, .primitive => Primitive, .fntype => FnType, + .intcast => IntCast, }; } @@ -243,6 +245,17 @@ pub const Inst = struct { cc: std.builtin.CallingConvention = .Unspecified, }, }; + + pub const IntCast = struct { + pub const base_tag = Tag.intcast; + base: Inst, + + positionals: struct { + dest_type: *Inst, + value: *Inst, + }, + kw_args: struct {}, + }; }; pub const ErrorMsg = struct { @@ -315,6 +328,7 @@ pub const Module = struct { .@"export" => return self.writeInstToStreamGeneric(stream, .@"export", decl, inst_table), .primitive => return self.writeInstToStreamGeneric(stream, .primitive, decl, inst_table), .fntype => return self.writeInstToStreamGeneric(stream, .fntype, decl, inst_table), + .intcast => return self.writeInstToStreamGeneric(stream, .intcast, decl, inst_table), } } diff --git a/src-self-hosted/type.zig b/src-self-hosted/type.zig index bea6aa75e3..f57bb58c71 100644 --- a/src-self-hosted/type.zig +++ b/src-self-hosted/type.zig @@ -2,6 +2,7 @@ const std = @import("std"); const Value = @import("value.zig").Value; const assert = std.debug.assert; const Allocator = std.mem.Allocator; +const Target = std.Target; /// This is the raw data, with no bookkeeping, no memory awareness, no de-duplication. /// It's important for this struct to be small. @@ -333,6 +334,44 @@ pub const Type = extern union { }; } + /// Asserts the type is a fixed-width integer. + pub fn intInfo(self: Type, target: Target) struct { signed: bool, bits: u16 } { + return switch (self.tag()) { + .@"f16", + .@"f32", + .@"f64", + .@"f128", + .@"c_longdouble", + .@"c_void", + .@"bool", + .@"void", + .@"type", + .@"anyerror", + .@"comptime_int", + .@"comptime_float", + .@"noreturn", + .fn_naked_noreturn_no_args, + .array, + .single_const_pointer, + .array_u8_sentinel_0, + .const_slice_u8, + => unreachable, + + .@"u8" => .{ .signed = false, .bits = 8 }, + .@"i8" => .{ .signed = true, .bits = 8 }, + .@"usize" => .{ .signed = false, .bits = target.cpu.arch.ptrBitWidth() }, + .@"isize" => .{ .signed = true, .bits = target.cpu.arch.ptrBitWidth() }, + .@"c_short" => .{ .signed = true, .bits = CInteger.short.sizeInBits(target) }, + .@"c_ushort" => .{ .signed = false, .bits = CInteger.ushort.sizeInBits(target) }, + .@"c_int" => .{ .signed = true, .bits = CInteger.int.sizeInBits(target) }, + .@"c_uint" => .{ .signed = false, .bits = CInteger.uint.sizeInBits(target) }, + .@"c_long" => .{ .signed = true, .bits = CInteger.long.sizeInBits(target) }, + .@"c_ulong" => .{ .signed = false, .bits = CInteger.ulong.sizeInBits(target) }, + .@"c_longlong" => .{ .signed = true, .bits = CInteger.longlong.sizeInBits(target) }, + .@"c_ulonglong" => .{ .signed = false, .bits = CInteger.ulonglong.sizeInBits(target) }, + }; + } + /// This enum does not directly correspond to `std.builtin.TypeId` because /// it has extra enum tags in it, as a way of using less memory. For example, /// even though Zig recognizes `*align(10) i32` and `*i32` both as Pointer types @@ -401,3 +440,126 @@ pub const Type = extern union { }; }; }; + +pub const CInteger = enum { + short, + ushort, + int, + uint, + long, + ulong, + longlong, + ulonglong, + + pub fn sizeInBits(self: CInteger, target: Target) u16 { + const arch = target.cpu.arch; + switch (target.os.tag) { + .freestanding, .other => switch (target.cpu.arch) { + .msp430 => switch (self) { + .short, + .ushort, + .int, + .uint, + => return 16, + .long, + .ulong, + => return 32, + .longlong, + .ulonglong, + => return 64, + }, + else => switch (self) { + .short, + .ushort, + => return 16, + .int, + .uint, + => return 32, + .long, + .ulong, + => return target.cpu.arch.ptrBitWidth(), + .longlong, + .ulonglong, + => return 64, + }, + }, + + .linux, + .macosx, + .freebsd, + .netbsd, + .dragonfly, + .openbsd, + .wasi, + .emscripten, + => switch (self) { + .short, + .ushort, + => return 16, + .int, + .uint, + => return 32, + .long, + .ulong, + => return target.cpu.arch.ptrBitWidth(), + .longlong, + .ulonglong, + => return 64, + }, + + .windows, .uefi => switch (self) { + .short, + .ushort, + => return 16, + .int, + .uint, + .long, + .ulong, + => return 32, + .longlong, + .ulonglong, + => return 64, + }, + + .ios => switch (self) { + .short, + .ushort, + => return 16, + .int, + .uint, + => return 32, + .long, + .ulong, + .longlong, + .ulonglong, + => return 64, + }, + + .ananas, + .cloudabi, + .fuchsia, + .kfreebsd, + .lv2, + .solaris, + .haiku, + .minix, + .rtems, + .nacl, + .cnk, + .aix, + .cuda, + .nvcl, + .amdhsa, + .ps4, + .elfiamcu, + .tvos, + .watchos, + .mesa3d, + .contiki, + .amdpal, + .hermit, + .hurd, + => @panic("TODO specify the C integer type sizes for this OS"), + } + } +}; diff --git a/src-self-hosted/value.zig b/src-self-hosted/value.zig index cd87fe24f2..1c3dee0fff 100644 --- a/src-self-hosted/value.zig +++ b/src-self-hosted/value.zig @@ -3,6 +3,7 @@ const Type = @import("type.zig").Type; const log2 = std.math.log2; const assert = std.debug.assert; const BigInt = std.math.big.Int; +const Target = std.Target; /// This is the raw data, with no bookkeeping, no memory awareness, /// no de-duplication, and no type system awareness. @@ -198,6 +199,78 @@ pub const Value = extern union { }; } + /// Asserts the value is an integer, and the destination type is ComptimeInt or Int. + pub fn intFitsInType(self: Value, ty: Type, target: Target) bool { + switch (self.tag()) { + .ty, + .u8_type, + .i8_type, + .isize_type, + .usize_type, + .c_short_type, + .c_ushort_type, + .c_int_type, + .c_uint_type, + .c_long_type, + .c_ulong_type, + .c_longlong_type, + .c_ulonglong_type, + .c_longdouble_type, + .f16_type, + .f32_type, + .f64_type, + .f128_type, + .c_void_type, + .bool_type, + .void_type, + .type_type, + .anyerror_type, + .comptime_int_type, + .comptime_float_type, + .noreturn_type, + .fn_naked_noreturn_no_args_type, + .const_slice_u8_type, + .void_value, + .noreturn_value, + .bool_true, + .bool_false, + .function, + .ref, + .bytes, + => unreachable, + + .int_u64 => switch (ty.zigTypeTag()) { + .Int => { + const x = self.cast(Payload.Int_u64).?.int; + const info = ty.intInfo(target); + const needed_bits = std.math.log2(x) + 1 + @boolToInt(info.signed); + return info.bits >= needed_bits; + }, + .ComptimeInt => return true, + else => unreachable, + }, + .int_i64 => switch (ty.zigTypeTag()) { + .Int => { + const x = self.cast(Payload.Int_i64).?.int; + const info = ty.intInfo(target); + if (!info.signed and x < 0) + return false; + @panic("TODO implement i64 intFitsInType"); + }, + .ComptimeInt => return true, + else => unreachable, + }, + .int_big => switch (ty.zigTypeTag()) { + .Int => { + const info = ty.intInfo(target); + return self.cast(Payload.IntBig).?.big_int.fitsInTwosComp(info.signed, info.bits); + }, + .ComptimeInt => return true, + else => unreachable, + }, + } + } + /// This type is not copyable since it may contain pointers to its inner data. pub const Payload = struct { tag: Tag, diff --git a/src/ir.cpp b/src/ir.cpp index 2732f4e023..3a8691867e 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -11289,9 +11289,9 @@ static bool ir_num_lit_fits_in_other_type(IrAnalyze *ira, IrInstGen *instruction Buf *val_buf = buf_alloc(); bigint_append_buf(val_buf, &const_val->data.x_bigint, 10); ir_add_error_node(ira, instruction->base.source_node, - buf_sprintf("integer value %s has no representation in type '%s'", - buf_ptr(val_buf), - buf_ptr(&other_type->name))); + buf_sprintf("type %s cannot represent integer value %s", + buf_ptr(&other_type->name), + buf_ptr(val_buf))); return false; } if (other_type->data.floating.bit_count >= const_val->type->data.floating.bit_count) {