ir: analyze int casting

master
Andrew Kelley 2020-04-21 19:48:59 -04:00
parent 0746028a2a
commit 2cdbb5f472
8 changed files with 303 additions and 174 deletions

View File

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

View File

@ -761,7 +761,7 @@ pub const Target = struct {
};
}
pub fn ptrBitWidth(arch: Arch) u32 {
pub fn ptrBitWidth(arch: Arch) u16 {
switch (arch) {
.avr,
.msp430,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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