Commit 2cdbb5f472

Andrew Kelley <andrew@ziglang.org>
2020-04-22 01:48:59
ir: analyze int casting
1 parent 0746028
Changed files (8)
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;
         }
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,
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) {
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),
         }
     }
 
src-self-hosted/c_int.zig
@@ -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"),
-        }
-    }
-};
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", .{});
     }
 
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"),
+        }
+    }
+};
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,