ir: analyze int casting
parent
0746028a2a
commit
2cdbb5f472
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -761,7 +761,7 @@ pub const Target = struct {
|
|||
};
|
||||
}
|
||||
|
||||
pub fn ptrBitWidth(arch: Arch) u32 {
|
||||
pub fn ptrBitWidth(arch: Arch) u16 {
|
||||
switch (arch) {
|
||||
.avr,
|
||||
.msp430,
|
||||
|
|
|
@ -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"),
|
||||
}
|
||||
}
|
||||
};
|
|
@ -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", .{});
|
||||
}
|
||||
|
||||
|
|
|
@ -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),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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"),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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) {
|
||||
|
|
Loading…
Reference in New Issue