From 874904e0d3bda301cb52e774fee8fad5bf499d24 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Wed, 8 Nov 2023 23:05:35 +0200 Subject: [PATCH] Value: implement remaining casts --- src/Parser.zig | 18 +++--- src/Type.zig | 5 +- src/Value.zig | 148 +++++++++++++++++++++++++------------------------ 3 files changed, 88 insertions(+), 83 deletions(-) diff --git a/src/Parser.zig b/src/Parser.zig index 30fa6e2d..72b77143 100644 --- a/src/Parser.zig +++ b/src/Parser.zig @@ -2519,7 +2519,7 @@ fn enumSpec(p: *Parser) Error!Type { else continue; - vals[i].intCast(field.ty, dest_ty, p.comp); + try vals[i].intCast(dest_ty, p.ctx()); types[i] = dest_ty; p.nodes.items(.ty)[@intFromEnum(field_nodes[i])] = dest_ty; field.ty = dest_ty; @@ -5180,7 +5180,7 @@ const Result = struct { try res.implicitCast(p, .int_to_bool); } else if (res.ty.isFloat()) { const old_value = res.val; - const value_change_kind = res.val.floatToInt(res.ty, bool_ty, p.comp); + const value_change_kind = try res.val.floatToInt(bool_ty, p.ctx()); try res.floatToIntWarning(p, bool_ty, old_value, value_change_kind, tok); if (!res.ty.isReal()) { res.ty = res.ty.makeReal(); @@ -5209,7 +5209,7 @@ const Result = struct { } } else if (res.ty.isFloat()) { const old_value = res.val; - const value_change_kind = res.val.floatToInt(res.ty, int_ty, p.comp); + const value_change_kind = try res.val.floatToInt(int_ty, p.ctx()); try res.floatToIntWarning(p, int_ty, old_value, value_change_kind, tok); const old_real = res.ty.isReal(); const new_real = int_ty.isReal(); @@ -5231,7 +5231,7 @@ const Result = struct { try res.implicitCast(p, .complex_float_to_complex_int); } } else if (!res.ty.eql(int_ty, p.comp, true)) { - res.val.intCast(res.ty, int_ty, p.comp); + try res.val.intCast(int_ty, p.ctx()); const old_real = res.ty.isReal(); const new_real = int_ty.isReal(); if (old_real and new_real) { @@ -5298,7 +5298,7 @@ const Result = struct { try res.implicitCast(p, .complex_int_to_complex_float); } } else if (!res.ty.eql(float_ty, p.comp, true)) { - res.val.floatCast(res.ty, float_ty, p.comp); + try res.val.floatCast(float_ty, p.ctx()); const old_real = res.ty.isReal(); const new_real = float_ty.isReal(); if (old_real and new_real) { @@ -5331,7 +5331,7 @@ const Result = struct { res.ty = ptr_ty; try res.implicitCast(p, .bool_to_pointer); } else if (res.ty.isInt()) { - res.val.intCast(res.ty, ptr_ty, p.comp); + try res.val.intCast(ptr_ty, p.ctx()); res.ty = ptr_ty; try res.implicitCast(p, .int_to_pointer); } @@ -5650,17 +5650,17 @@ const Result = struct { res.val.boolCast(); } else if (old_float and new_int) { // Explicit cast, no conversion warning - _ = res.val.floatToInt(res.ty, to, p.comp); + _ = try res.val.floatToInt(to, p.ctx()); } else if (new_float and old_int) { try res.val.intToFloat(to, p.ctx()); } else if (new_float and old_float) { - res.val.floatCast(res.ty, to, p.comp); + try res.val.floatCast(to, p.ctx()); } else if (old_int and new_int) { if (to.hasIncompleteSize()) { try p.errStr(.cast_to_incomplete_type, tok, try p.typeStr(to)); return error.ParsingFailed; } - res.val.intCast(res.ty, to, p.comp); + try res.val.intCast(to, p.ctx()); } } else if (to.get(.@"union")) |union_ty| { if (union_ty.data.record.hasFieldOfType(res.ty, p.comp)) { diff --git a/src/Type.zig b/src/Type.zig index 5df31566..0fa0ebb2 100644 --- a/src/Type.zig +++ b/src/Type.zig @@ -629,7 +629,10 @@ pub fn signedness(ty: Type, comp: *const Compilation) std.builtin.Signedness { // zig fmt: off .char, .complex_char => return comp.getCharSignedness(), .uchar, .ushort, .uint, .ulong, .ulong_long, .bool, .complex_uchar, .complex_ushort, - .complex_uint, .complex_ulong, .complex_ulong_long, .complex_uint128 => .unsigned, + .complex_uint, .complex_ulong, .complex_ulong_long, .complex_uint128, + .pointer, .decayed_array, .decayed_static_array, .decayed_incomplete_array, + .decayed_variable_len_array, .decayed_unspecified_variable_len_array, + .decayed_typeof_type, .decayed_typeof_expr => .unsigned, // zig fmt: on .bit_int, .complex_bit_int => ty.data.int.signedness, .typeof_type => ty.data.sub_type.signedness(comp), diff --git a/src/Value.zig b/src/Value.zig index 0355f2ee..4993b76d 100644 --- a/src/Value.zig +++ b/src/Value.zig @@ -207,61 +207,58 @@ pub const FloatToIntChangeKind = enum { value_changed, }; -fn floatToIntExtra(comptime FloatTy: type, int_ty_signedness: std.builtin.Signedness, int_ty_size: u16, v: *Value) FloatToIntChangeKind { - const float_val = v.getFloat(FloatTy); +/// Converts the stored value from a float to an integer. +/// `.none` value remains unchanged. +pub fn floatToInt(v: *Value, dest_ty: Type, ctx: Context) !FloatToIntChangeKind { + if (v.opt_ref == .none) return .none; + + const float_val = v.toFloat(f128, ctx); const was_zero = float_val == 0; - const had_fraction = std.math.modf(float_val).fpart != 0; - - switch (int_ty_signedness) { - inline else => |signedness| switch (int_ty_size) { - inline 1, 2, 4, 8 => |bytecount| { - const IntTy = std.meta.Int(signedness, bytecount * 8); - - const intVal = std.math.lossyCast(IntTy, float_val); - v.* = int(intVal); - if (!was_zero and v.isZero()) return .nonzero_to_zero; - if (float_val <= std.math.minInt(IntTy) or float_val >= std.math.maxInt(IntTy)) return .out_of_range; - if (had_fraction) return .value_changed; - return .none; - }, - else => unreachable, - }, - } -} -/// Converts the stored value from a float to an integer. -/// `.unavailable` value remains unchanged. -pub fn floatToInt(v: *Value, old_ty: Type, new_ty: Type, comp: *Compilation) FloatToIntChangeKind { - assert(old_ty.isFloat()); - if (v.tag == .unavailable) return .none; - if (new_ty.is(.bool)) { - const was_zero = v.isZero(); - const was_one = v.getFloat(f64) == 1.0; - v.toBool(); + if (dest_ty.is(.bool)) { + const was_one = float_val == 1.0; + v.* = fromBool(!was_zero); if (was_zero or was_one) return .none; return .value_changed; - } else if (new_ty.isUnsignedInt(comp) and v.data.float < 0) { - v.* = int(0); + } else if (dest_ty.isUnsignedInt(ctx.comp) and v.compare(.lt, zero, ctx)) { + v.* = zero; return .out_of_range; - } else if (!std.math.isFinite(v.data.float)) { - v.tag = .unavailable; - return .overflow; } - const old_size = old_ty.sizeof(comp).?; - const new_size: u16 = @intCast(new_ty.sizeof(comp).?); - if (new_ty.isUnsignedInt(comp)) switch (old_size) { - 1 => unreachable, // promoted to int - 2 => unreachable, // promoted to int - 4 => return floatToIntExtra(f32, .unsigned, new_size, v), - 8 => return floatToIntExtra(f64, .unsigned, new_size, v), - else => unreachable, - } else switch (old_size) { - 1 => unreachable, // promoted to int - 2 => unreachable, // promoted to int - 4 => return floatToIntExtra(f32, .signed, new_size, v), - 8 => return floatToIntExtra(f64, .signed, new_size, v), - else => unreachable, + + const had_fraction = @rem(float_val, 1) != 0; + const is_negative = std.math.signbit(float_val); + const floored = @floor(@abs(float_val)); + + var rational = try std.math.big.Rational.init(ctx.comp.gpa); + defer rational.q.deinit(); + rational.setFloat(f128, floored) catch |err| switch (err) { + error.NonFiniteFloat => { + v.* = .{}; + return .overflow; + }, + error.OutOfMemory => return error.OutOfMemory, + }; + + // The float is reduced in rational.setFloat, so we assert that denominator is equal to one + const big_one = std.math.big.int.Const{ .limbs = &.{1}, .positive = true }; + assert(rational.q.toConst().eqlAbs(big_one)); + + if (is_negative) { + rational.negate(); } + + const signedness = dest_ty.signedness(ctx.comp); + const bits = dest_ty.bitSizeof(ctx.comp).?; + + // rational.p.truncate(rational.p.toConst(), signedness: Signedness, bit_count: usize) + const fits = rational.p.fitsInTwosComp(signedness, bits); + try rational.p.truncate(&rational.p, signedness, bits); + v.* = try ctx.intern(.{ .int = .{ .big_int = rational.p.toConst() } }); + + if (!was_zero and v.isZero()) return .nonzero_to_zero; + if (!fits) return .out_of_range; + if (had_fraction) return .value_changed; + return .none; } /// Converts the stored value from an integer to a float. @@ -297,34 +294,38 @@ pub fn intToFloat(v: *Value, dest_ty: Type, ctx: Context) !void { } /// Truncates or extends bits based on type. -/// old_ty is only used for size. -pub fn intCast(v: *Value, old_ty: Type, new_ty: Type, comp: *Compilation) void { - // assert(old_ty.isInt() and new_ty.isInt()); - if (v.tag == .unavailable) return; - if (new_ty.is(.bool)) return v.toBool(); - if (!old_ty.isUnsignedInt(comp)) { - const size = new_ty.sizeof(comp).?; - switch (size) { - 1 => v.* = int(@as(u8, @truncate(@as(u64, @bitCast(v.signExtend(old_ty, comp)))))), - 2 => v.* = int(@as(u16, @truncate(@as(u64, @bitCast(v.signExtend(old_ty, comp)))))), - 4 => v.* = int(@as(u32, @truncate(@as(u64, @bitCast(v.signExtend(old_ty, comp)))))), - 8 => return, - else => unreachable, - } - } +/// `.none` value remains unchanged. +pub fn intCast(v: *Value, dest_ty: Type, ctx: Context) !void { + if (v.opt_ref == .none) return; + const bits = dest_ty.bitSizeof(ctx.comp).?; + var space: BigIntSpace = undefined; + const big = v.toBigInt(&space, ctx); + + const limbs = try ctx.comp.gpa.alloc( + std.math.big.Limb, + std.math.big.int.calcTwosCompLimbCount(bits), + ); + defer ctx.comp.gpa.free(limbs); + var result_bigint = std.math.big.int.Mutable{ .limbs = limbs, .positive = undefined, .len = undefined }; + result_bigint.truncate(big, dest_ty.signedness(ctx.comp), bits); + + v.* = try ctx.intern(.{ .int = .{ .big_int = result_bigint.toConst() } }); } /// Converts the stored value from an integer to a float. -/// `.unavailable` value remains unchanged. -pub fn floatCast(v: *Value, old_ty: Type, new_ty: Type, comp: *Compilation) void { - assert(old_ty.isFloat() and new_ty.isFloat()); - if (v.tag == .unavailable) return; - const size = new_ty.sizeof(comp).?; - if (!new_ty.isReal() or size > 8) { - v.tag = .unavailable; - } else if (size == 32) { - v.* = float(@as(f32, @floatCast(v.data.float))); - } +/// `.none` value remains unchanged. +pub fn floatCast(v: *Value, dest_ty: Type, ctx: Context) !void { + if (v.opt_ref == .none) return; + const bits = dest_ty.bitSizeof(ctx.comp).?; + const f: Interner.Key.Float = switch (bits) { + 16 => .{ .f16 = v.toFloat(f16, ctx) }, + 32 => .{ .f32 = v.toFloat(f32, ctx) }, + 64 => .{ .f64 = v.toFloat(f64, ctx) }, + 80 => .{ .f80 = v.toFloat(f80, ctx) }, + 128 => .{ .f128 = v.toFloat(f128, ctx) }, + else => unreachable, + }; + v.* = try ctx.intern(.{ .float = f }); } pub fn toFloat(v: Value, comptime T: type, ctx: Context) T { @@ -379,6 +380,7 @@ pub fn fromBool(b: bool) Value { } pub fn toBool(v: Value) bool { + // TODO this doesn't work for floats return v.ref() != .zero; }