Merge pull request #24188 from mlugg/intfromfloat-safety

Absorb std.math.big.rational logic into std.math.big.int; fix `@intFromFloat` safety check
This commit is contained in:
Matthew Lugg 2025-06-17 11:02:03 +01:00 committed by GitHub
commit 561fdd0ed3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
39 changed files with 1339 additions and 1080 deletions

View File

@ -148,35 +148,25 @@ pub fn floatToInt(v: *Value, dest_ty: Type, comp: *Compilation) !FloatToIntChang
return .out_of_range; return .out_of_range;
} }
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(comp.gpa);
defer rational.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 = BigIntConst{ .limbs = &.{1}, .positive = true };
assert(rational.q.toConst().eqlAbs(big_one));
if (is_negative) {
rational.negate();
}
const signedness = dest_ty.signedness(comp); const signedness = dest_ty.signedness(comp);
const bits: usize = @intCast(dest_ty.bitSizeof(comp).?); const bits: usize = @intCast(dest_ty.bitSizeof(comp).?);
// rational.p.truncate(rational.p.toConst(), signedness: Signedness, bit_count: usize) var big_int: std.math.big.int.Mutable = .{
const fits = rational.p.fitsInTwosComp(signedness, bits); .limbs = try comp.gpa.alloc(std.math.big.Limb, @max(
v.* = try intern(comp, .{ .int = .{ .big_int = rational.p.toConst() } }); std.math.big.int.calcLimbLen(float_val),
try rational.p.truncate(&rational.p, signedness, bits); std.math.big.int.calcTwosCompLimbCount(bits),
)),
.len = undefined,
.positive = undefined,
};
const had_fraction = switch (big_int.setFloat(float_val, .trunc)) {
.inexact => true,
.exact => false,
};
const fits = big_int.toConst().fitsInTwosComp(signedness, bits);
v.* = try intern(comp, .{ .int = .{ .big_int = big_int.toConst() } });
big_int.truncate(big_int.toConst(), signedness, bits);
if (!was_zero and v.isZero(comp)) return .nonzero_to_zero; if (!was_zero and v.isZero(comp)) return .nonzero_to_zero;
if (!fits) return .out_of_range; if (!fits) return .out_of_range;

View File

@ -45,6 +45,7 @@ pub const rad_per_deg = 0.017453292519943295769236907684886127134428718885417254
/// 180.0/pi /// 180.0/pi
pub const deg_per_rad = 57.295779513082320876798154814105170332405472466564321549160243861; pub const deg_per_rad = 57.295779513082320876798154814105170332405472466564321549160243861;
pub const FloatRepr = float.FloatRepr;
pub const floatExponentBits = float.floatExponentBits; pub const floatExponentBits = float.floatExponentBits;
pub const floatMantissaBits = float.floatMantissaBits; pub const floatMantissaBits = float.floatMantissaBits;
pub const floatFractionalBits = float.floatFractionalBits; pub const floatFractionalBits = float.floatFractionalBits;

View File

@ -1,7 +1,6 @@
const std = @import("../std.zig"); const std = @import("../std.zig");
const assert = std.debug.assert; const assert = std.debug.assert;
pub const Rational = @import("big/rational.zig").Rational;
pub const int = @import("big/int.zig"); pub const int = @import("big/int.zig");
pub const Limb = usize; pub const Limb = usize;
const limb_info = @typeInfo(Limb).int; const limb_info = @typeInfo(Limb).int;
@ -18,7 +17,6 @@ comptime {
test { test {
_ = int; _ = int;
_ = Rational;
_ = Limb; _ = Limb;
_ = SignedLimb; _ = SignedLimb;
_ = DoubleLimb; _ = DoubleLimb;

View File

@ -18,17 +18,28 @@ const Signedness = std.builtin.Signedness;
const native_endian = builtin.cpu.arch.endian(); const native_endian = builtin.cpu.arch.endian();
/// Returns the number of limbs needed to store `scalar`, which must be a /// Returns the number of limbs needed to store `scalar`, which must be a
/// primitive integer value. /// primitive integer or float value.
/// Note: A comptime-known upper bound of this value that may be used /// Note: A comptime-known upper bound of this value that may be used
/// instead if `scalar` is not already comptime-known is /// instead if `scalar` is not already comptime-known is
/// `calcTwosCompLimbCount(@typeInfo(@TypeOf(scalar)).int.bits)` /// `calcTwosCompLimbCount(@typeInfo(@TypeOf(scalar)).int.bits)`
pub fn calcLimbLen(scalar: anytype) usize { pub fn calcLimbLen(scalar: anytype) usize {
if (scalar == 0) { switch (@typeInfo(@TypeOf(scalar))) {
return 1; .int, .comptime_int => {
} if (scalar == 0) return 1;
const w_value = @abs(scalar); const w_value = @abs(scalar);
return @as(usize, @intCast(@divFloor(@as(Limb, @intCast(math.log2(w_value))), limb_bits) + 1)); return @as(usize, @intCast(@divFloor(@as(Limb, @intCast(math.log2(w_value))), limb_bits) + 1));
},
.float => {
const repr: std.math.FloatRepr(@TypeOf(scalar)) = @bitCast(scalar);
return switch (repr.exponent) {
.denormal => 1,
else => return calcNonZeroTwosCompLimbCount(@as(usize, 2) + @max(repr.exponent.unbias(), 0)),
.infinite => 0,
};
},
.comptime_float => return calcLimbLen(@as(f128, scalar)),
else => @compileError("expected float or int, got " ++ @typeName(@TypeOf(scalar))),
}
} }
pub fn calcToStringLimbsBufferLen(a_len: usize, base: u8) usize { pub fn calcToStringLimbsBufferLen(a_len: usize, base: u8) usize {
@ -134,6 +145,22 @@ pub const TwosCompIntLimit = enum {
max, max,
}; };
pub const Round = enum {
/// Round to the nearest representable value, with ties broken by the representation
/// that ends with a 0 bit.
nearest_even,
/// Round away from zero.
away,
/// Round towards zero.
trunc,
/// Round towards negative infinity.
floor,
/// Round towards positive infinity.
ceil,
};
pub const Exactness = enum { inexact, exact };
/// A arbitrary-precision big integer, with a fixed set of mutable limbs. /// A arbitrary-precision big integer, with a fixed set of mutable limbs.
pub const Mutable = struct { pub const Mutable = struct {
/// Raw digits. These are: /// Raw digits. These are:
@ -155,6 +182,20 @@ pub const Mutable = struct {
}; };
} }
pub const ConvertError = Const.ConvertError;
/// Convert `self` to `Int`.
///
/// Returns an error if self cannot be narrowed into the requested type without truncation.
pub fn toInt(self: Mutable, comptime Int: type) ConvertError!Int {
return self.toConst().toInt(Int);
}
/// Convert `self` to `Float`.
pub fn toFloat(self: Mutable, comptime Float: type, round: Round) struct { Float, Exactness } {
return self.toConst().toFloat(Float, round);
}
/// Returns true if `a == 0`. /// Returns true if `a == 0`.
pub fn eqlZero(self: Mutable) bool { pub fn eqlZero(self: Mutable) bool {
return self.toConst().eqlZero(); return self.toConst().eqlZero();
@ -401,6 +442,65 @@ pub const Mutable = struct {
} }
} }
/// Sets the Mutable to a float value rounded according to `round`.
/// Returns whether the conversion was exact (`round` had no effect on the result).
pub fn setFloat(self: *Mutable, value: anytype, round: Round) Exactness {
const Float = @TypeOf(value);
if (Float == comptime_float) return self.setFloat(@as(f128, value), round);
const abs_value = @abs(value);
if (abs_value < 1.0) {
if (abs_value == 0.0) {
self.set(0);
return .exact;
}
self.set(@as(i2, round: switch (round) {
.nearest_even => if (abs_value <= 0.5) 0 else continue :round .away,
.away => if (value < 0.0) -1 else 1,
.trunc => 0,
.floor => -@as(i2, @intFromBool(value < 0.0)),
.ceil => @intFromBool(value > 0.0),
}));
return .inexact;
}
const Repr = std.math.FloatRepr(Float);
const repr: Repr = @bitCast(value);
const exponent = repr.exponent.unbias();
assert(exponent >= 0);
const int_bit: Repr.Mantissa = 1 << (@bitSizeOf(Repr.Mantissa) - 1);
const mantissa = int_bit | repr.mantissa;
if (exponent >= @bitSizeOf(Repr.Normalized.Fraction)) {
self.set(mantissa);
self.shiftLeft(self.toConst(), @intCast(exponent - @bitSizeOf(Repr.Normalized.Fraction)));
self.positive = repr.sign == .positive;
return .exact;
}
self.set(mantissa >> @intCast(@bitSizeOf(Repr.Normalized.Fraction) - exponent));
const round_bits: Repr.Normalized.Fraction = @truncate(mantissa << @intCast(exponent));
if (round_bits == 0) {
self.positive = repr.sign == .positive;
return .exact;
}
round: switch (round) {
.nearest_even => {
const half: Repr.Normalized.Fraction = 1 << (@bitSizeOf(Repr.Normalized.Fraction) - 1);
if (round_bits >= half) self.addScalar(self.toConst(), 1);
if (round_bits == half) self.limbs[0] &= ~@as(Limb, 1);
},
.away => self.addScalar(self.toConst(), 1),
.trunc => {},
.floor => switch (repr.sign) {
.positive => {},
.negative => continue :round .away,
},
.ceil => switch (repr.sign) {
.positive => continue :round .away,
.negative => {},
},
}
self.positive = repr.sign == .positive;
return .inexact;
}
/// r = a + scalar /// r = a + scalar
/// ///
/// r and a may be aliases. /// r and a may be aliases.
@ -2117,25 +2217,25 @@ pub const Const = struct {
/// Deprecated; use `toInt`. /// Deprecated; use `toInt`.
pub const to = toInt; pub const to = toInt;
/// Convert self to integer type T. /// Convert `self` to `Int`.
/// ///
/// Returns an error if self cannot be narrowed into the requested type without truncation. /// Returns an error if self cannot be narrowed into the requested type without truncation.
pub fn toInt(self: Const, comptime T: type) ConvertError!T { pub fn toInt(self: Const, comptime Int: type) ConvertError!Int {
switch (@typeInfo(T)) { switch (@typeInfo(Int)) {
.int => |info| { .int => |info| {
// Make sure -0 is handled correctly. // Make sure -0 is handled correctly.
if (self.eqlZero()) return 0; if (self.eqlZero()) return 0;
const UT = std.meta.Int(.unsigned, info.bits); const Unsigned = std.meta.Int(.unsigned, info.bits);
if (!self.fitsInTwosComp(info.signedness, info.bits)) { if (!self.fitsInTwosComp(info.signedness, info.bits)) {
return error.TargetTooSmall; return error.TargetTooSmall;
} }
var r: UT = 0; var r: Unsigned = 0;
if (@sizeOf(UT) <= @sizeOf(Limb)) { if (@sizeOf(Unsigned) <= @sizeOf(Limb)) {
r = @as(UT, @intCast(self.limbs[0])); r = @intCast(self.limbs[0]);
} else { } else {
for (self.limbs[0..self.limbs.len], 0..) |_, ri| { for (self.limbs[0..self.limbs.len], 0..) |_, ri| {
const limb = self.limbs[self.limbs.len - ri - 1]; const limb = self.limbs[self.limbs.len - ri - 1];
@ -2145,40 +2245,76 @@ pub const Const = struct {
} }
if (info.signedness == .unsigned) { if (info.signedness == .unsigned) {
return if (self.positive) @as(T, @intCast(r)) else error.NegativeIntoUnsigned; return if (self.positive) @intCast(r) else error.NegativeIntoUnsigned;
} else { } else {
if (self.positive) { if (self.positive) {
return @intCast(r); return @intCast(r);
} else { } else {
if (math.cast(T, r)) |ok| { if (math.cast(Int, r)) |ok| {
return -ok; return -ok;
} else { } else {
return minInt(T); return minInt(Int);
} }
} }
} }
}, },
else => @compileError("expected int type, found '" ++ @typeName(T) ++ "'"), else => @compileError("expected int type, found '" ++ @typeName(Int) ++ "'"),
} }
} }
/// Convert self to float type T. /// Convert self to `Float`.
pub fn toFloat(self: Const, comptime T: type) T { pub fn toFloat(self: Const, comptime Float: type, round: Round) struct { Float, Exactness } {
if (self.limbs.len == 0) return 0; if (Float == comptime_float) return self.toFloat(f128, round);
const normalized_abs: Const = .{
.limbs = self.limbs[0..llnormalize(self.limbs)],
.positive = true,
};
if (normalized_abs.eqlZero()) return .{ if (self.positive) 0.0 else -0.0, .exact };
const base = std.math.maxInt(std.math.big.Limb) + 1; const Repr = std.math.FloatRepr(Float);
var result: f128 = 0; var mantissa_limbs: [calcNonZeroTwosCompLimbCount(1 + @bitSizeOf(Repr.Mantissa))]Limb = undefined;
var i: usize = self.limbs.len; var mantissa: Mutable = .{
while (i != 0) { .limbs = &mantissa_limbs,
i -= 1; .positive = undefined,
const limb: f128 = @floatFromInt(self.limbs[i]); .len = undefined,
result = @mulAdd(f128, base, result, limb); };
var exponent = normalized_abs.bitCountAbs() - 1;
const exactness: Exactness = exactness: {
if (exponent <= @bitSizeOf(Repr.Normalized.Fraction)) {
mantissa.shiftLeft(normalized_abs, @intCast(@bitSizeOf(Repr.Normalized.Fraction) - exponent));
break :exactness .exact;
} }
if (self.positive) { const shift: usize = @intCast(exponent - @bitSizeOf(Repr.Normalized.Fraction));
return @floatCast(result); mantissa.shiftRight(normalized_abs, shift);
} else { const final_limb_index = (shift - 1) / limb_bits;
return @floatCast(-result); const round_bits = normalized_abs.limbs[final_limb_index] << @truncate(-%shift) |
@intFromBool(!std.mem.allEqual(Limb, normalized_abs.limbs[0..final_limb_index], 0));
if (round_bits == 0) break :exactness .exact;
round: switch (round) {
.nearest_even => {
const half: Limb = 1 << (limb_bits - 1);
if (round_bits >= half) mantissa.addScalar(mantissa.toConst(), 1);
if (round_bits == half) mantissa.limbs[0] &= ~@as(Limb, 1);
},
.away => mantissa.addScalar(mantissa.toConst(), 1),
.trunc => {},
.floor => if (!self.positive) continue :round .away,
.ceil => if (self.positive) continue :round .away,
} }
break :exactness .inexact;
};
const normalized_res: Repr.Normalized = .{
.fraction = @truncate(mantissa.toInt(Repr.Mantissa) catch |err| switch (err) {
error.NegativeIntoUnsigned => unreachable,
error.TargetTooSmall => fraction: {
assert(mantissa.toConst().orderAgainstScalar(1 << @bitSizeOf(Repr.Mantissa)).compare(.eq));
exponent += 1;
break :fraction 1 << (@bitSizeOf(Repr.Mantissa) - 1);
},
}),
.exponent = std.math.lossyCast(Repr.Normalized.Exponent, exponent),
};
return .{ normalized_res.reconstruct(if (self.positive) .positive else .negative), exactness };
} }
/// To allow `std.fmt.format` to work with this type. /// To allow `std.fmt.format` to work with this type.
@ -2739,16 +2875,16 @@ pub const Managed = struct {
/// Deprecated; use `toInt`. /// Deprecated; use `toInt`.
pub const to = toInt; pub const to = toInt;
/// Convert self to integer type T. /// Convert `self` to `Int`.
/// ///
/// Returns an error if self cannot be narrowed into the requested type without truncation. /// Returns an error if self cannot be narrowed into the requested type without truncation.
pub fn toInt(self: Managed, comptime T: type) ConvertError!T { pub fn toInt(self: Managed, comptime Int: type) ConvertError!Int {
return self.toConst().toInt(T); return self.toConst().toInt(Int);
} }
/// Convert self to float type T. /// Convert `self` to `Float`.
pub fn toFloat(self: Managed, comptime T: type) T { pub fn toFloat(self: Managed, comptime Float: type, round: Round) struct { Float, Exactness } {
return self.toConst().toFloat(T); return self.toConst().toFloat(Float, round);
} }
/// Set self from the string representation `value`. /// Set self from the string representation `value`.
@ -3807,7 +3943,7 @@ fn llshr(r: []Limb, a: []const Limb, shift: usize) usize {
// if the most significant limb becomes 0 after the shift // if the most significant limb becomes 0 after the shift
const shrink = a[a.len - 1] >> bit_shift == 0; const shrink = a[a.len - 1] >> bit_shift == 0;
std.debug.assert(r.len >= a.len - @intFromBool(!shrink)); std.debug.assert(r.len >= a.len - @intFromBool(shrink));
var i: usize = 0; var i: usize = 0;
while (i < a.len - 1) : (i += 1) { while (i < a.len - 1) : (i += 1) {
@ -4240,7 +4376,7 @@ test {
const testing_allocator = std.testing.allocator; const testing_allocator = std.testing.allocator;
test "llshl shift by whole number of limb" { test "llshl shift by whole number of limb" {
const padding = std.math.maxInt(Limb); const padding = maxInt(Limb);
var r: [10]Limb = @splat(padding); var r: [10]Limb = @splat(padding);
@ -4390,8 +4526,8 @@ test "llshr to 0" {
try testOneShiftCase(.llshr, .{1, &.{0}, &.{1}}); try testOneShiftCase(.llshr, .{1, &.{0}, &.{1}});
try testOneShiftCase(.llshr, .{5, &.{0}, &.{1}}); try testOneShiftCase(.llshr, .{5, &.{0}, &.{1}});
try testOneShiftCase(.llshr, .{65, &.{0}, &.{0, 1}}); try testOneShiftCase(.llshr, .{65, &.{0}, &.{0, 1}});
try testOneShiftCase(.llshr, .{193, &.{0}, &.{0, 0, std.math.maxInt(Limb)}}); try testOneShiftCase(.llshr, .{193, &.{0}, &.{0, 0, maxInt(Limb)}});
try testOneShiftCase(.llshr, .{193, &.{0}, &.{std.math.maxInt(Limb), 1, std.math.maxInt(Limb)}}); try testOneShiftCase(.llshr, .{193, &.{0}, &.{maxInt(Limb), 1, maxInt(Limb)}});
try testOneShiftCase(.llshr, .{193, &.{0}, &.{0xdeadbeef, 0xabcdefab, 0x1234}}); try testOneShiftCase(.llshr, .{193, &.{0}, &.{0xdeadbeef, 0xabcdefab, 0x1234}});
// zig fmt: on // zig fmt: on
} }
@ -4475,7 +4611,7 @@ fn testOneShiftCase(comptime function: enum { llshr, llshl }, case: Case) !void
} }
fn testOneShiftCaseNoAliasing(func: fn ([]Limb, []const Limb, usize) usize, case: Case) !void { fn testOneShiftCaseNoAliasing(func: fn ([]Limb, []const Limb, usize) usize, case: Case) !void {
const padding = std.math.maxInt(Limb); const padding = maxInt(Limb);
var r: [20]Limb = @splat(padding); var r: [20]Limb = @splat(padding);
const shift = case[0]; const shift = case[0];
@ -4492,7 +4628,7 @@ fn testOneShiftCaseNoAliasing(func: fn ([]Limb, []const Limb, usize) usize, case
} }
fn testOneShiftCaseAliasing(func: fn ([]Limb, []const Limb, usize) usize, case: Case, shift_direction: isize) !void { fn testOneShiftCaseAliasing(func: fn ([]Limb, []const Limb, usize) usize, case: Case, shift_direction: isize) !void {
const padding = std.math.maxInt(Limb); const padding = maxInt(Limb);
var r: [60]Limb = @splat(padding); var r: [60]Limb = @splat(padding);
const base = 20; const base = 20;

View File

@ -17,6 +17,12 @@ const minInt = std.math.minInt;
// They will still run on larger than this and should pass, but the multi-limb code-paths // They will still run on larger than this and should pass, but the multi-limb code-paths
// may be untested in some cases. // may be untested in some cases.
fn expectNormalized(expected: comptime_int, actual: std.math.big.int.Const) !void {
try testing.expectEqual(expected >= 0, actual.positive);
try testing.expectEqual(std.math.big.int.calcLimbLen(expected), actual.limbs.len);
try testing.expect(actual.orderAgainstScalar(expected).compare(.eq));
}
test "comptime_int set" { test "comptime_int set" {
comptime var s = 0xefffffff00000001eeeeeeefaaaaaaab; comptime var s = 0xefffffff00000001eeeeeeefaaaaaaab;
var a = try Managed.initSet(testing.allocator, s); var a = try Managed.initSet(testing.allocator, s);
@ -85,6 +91,408 @@ test "to target too small error" {
try testing.expectError(error.TargetTooSmall, a.toInt(u8)); try testing.expectError(error.TargetTooSmall, a.toInt(u8));
} }
fn setFloat(comptime Float: type) !void {
var res_limbs: [std.math.big.int.calcNonZeroTwosCompLimbCount(11)]Limb = undefined;
var res: Mutable = .{
.limbs = &res_limbs,
.len = undefined,
.positive = undefined,
};
try testing.expectEqual(.exact, res.setFloat(@as(Float, -0x1p10), .nearest_even));
try expectNormalized(-1 << 10, res.toConst());
try testing.expectEqual(.exact, res.setFloat(@as(Float, -0x1p10), .away));
try expectNormalized(-1 << 10, res.toConst());
try testing.expectEqual(.exact, res.setFloat(@as(Float, -0x1p10), .trunc));
try expectNormalized(-1 << 10, res.toConst());
try testing.expectEqual(.exact, res.setFloat(@as(Float, -0x1p10), .floor));
try expectNormalized(-1 << 10, res.toConst());
try testing.expectEqual(.exact, res.setFloat(@as(Float, -0x1p10), .ceil));
try expectNormalized(-1 << 10, res.toConst());
try testing.expectEqual(.exact, res.setFloat(@as(Float, -2.0), .nearest_even));
try expectNormalized(-2, res.toConst());
try testing.expectEqual(.exact, res.setFloat(@as(Float, -2.0), .away));
try expectNormalized(-2, res.toConst());
try testing.expectEqual(.exact, res.setFloat(@as(Float, -2.0), .trunc));
try expectNormalized(-2, res.toConst());
try testing.expectEqual(.exact, res.setFloat(@as(Float, -2.0), .floor));
try expectNormalized(-2, res.toConst());
try testing.expectEqual(.exact, res.setFloat(@as(Float, -2.0), .ceil));
try expectNormalized(-2, res.toConst());
try testing.expectEqual(.inexact, res.setFloat(@as(Float, -1.5), .nearest_even));
try expectNormalized(-2, res.toConst());
try testing.expectEqual(.inexact, res.setFloat(@as(Float, -1.5), .away));
try expectNormalized(-2, res.toConst());
try testing.expectEqual(.inexact, res.setFloat(@as(Float, -1.5), .trunc));
try expectNormalized(-1, res.toConst());
try testing.expectEqual(.inexact, res.setFloat(@as(Float, -1.5), .floor));
try expectNormalized(-2, res.toConst());
try testing.expectEqual(.inexact, res.setFloat(@as(Float, -1.5), .ceil));
try expectNormalized(-1, res.toConst());
try testing.expectEqual(.exact, res.setFloat(@as(Float, -1.0), .nearest_even));
try expectNormalized(-1, res.toConst());
try testing.expectEqual(.exact, res.setFloat(@as(Float, -1.0), .away));
try expectNormalized(-1, res.toConst());
try testing.expectEqual(.exact, res.setFloat(@as(Float, -1.0), .trunc));
try expectNormalized(-1, res.toConst());
try testing.expectEqual(.exact, res.setFloat(@as(Float, -1.0), .floor));
try expectNormalized(-1, res.toConst());
try testing.expectEqual(.exact, res.setFloat(@as(Float, -1.0), .ceil));
try expectNormalized(-1, res.toConst());
try testing.expectEqual(.inexact, res.setFloat(@as(Float, -0.75), .nearest_even));
try expectNormalized(-1, res.toConst());
try testing.expectEqual(.inexact, res.setFloat(@as(Float, -0.75), .away));
try expectNormalized(-1, res.toConst());
try testing.expectEqual(.inexact, res.setFloat(@as(Float, -0.75), .trunc));
try expectNormalized(0, res.toConst());
try testing.expectEqual(.inexact, res.setFloat(@as(Float, -0.75), .floor));
try expectNormalized(-1, res.toConst());
try testing.expectEqual(.inexact, res.setFloat(@as(Float, -0.75), .ceil));
try expectNormalized(0, res.toConst());
try testing.expectEqual(.inexact, res.setFloat(@as(Float, -0.5), .nearest_even));
try expectNormalized(0, res.toConst());
try testing.expectEqual(.inexact, res.setFloat(@as(Float, -0.5), .away));
try expectNormalized(-1, res.toConst());
try testing.expectEqual(.inexact, res.setFloat(@as(Float, -0.5), .trunc));
try expectNormalized(0, res.toConst());
try testing.expectEqual(.inexact, res.setFloat(@as(Float, -0.5), .floor));
try expectNormalized(-1, res.toConst());
try testing.expectEqual(.inexact, res.setFloat(@as(Float, -0.5), .ceil));
try expectNormalized(0, res.toConst());
try testing.expectEqual(.inexact, res.setFloat(@as(Float, -0.25), .nearest_even));
try expectNormalized(0, res.toConst());
try testing.expectEqual(.inexact, res.setFloat(@as(Float, -0.25), .away));
try expectNormalized(-1, res.toConst());
try testing.expectEqual(.inexact, res.setFloat(@as(Float, -0.25), .trunc));
try expectNormalized(0, res.toConst());
try testing.expectEqual(.inexact, res.setFloat(@as(Float, -0.25), .floor));
try expectNormalized(-1, res.toConst());
try testing.expectEqual(.inexact, res.setFloat(@as(Float, -0.25), .ceil));
try expectNormalized(0, res.toConst());
try testing.expectEqual(.exact, res.setFloat(@as(Float, -0.0), .nearest_even));
try expectNormalized(0, res.toConst());
try testing.expectEqual(.exact, res.setFloat(@as(Float, -0.0), .away));
try expectNormalized(0, res.toConst());
try testing.expectEqual(.exact, res.setFloat(@as(Float, -0.0), .trunc));
try expectNormalized(0, res.toConst());
try testing.expectEqual(.exact, res.setFloat(@as(Float, -0.0), .floor));
try expectNormalized(0, res.toConst());
try testing.expectEqual(.exact, res.setFloat(@as(Float, -0.0), .ceil));
try expectNormalized(0, res.toConst());
try testing.expectEqual(.exact, res.setFloat(@as(Float, 0.0), .nearest_even));
try expectNormalized(0, res.toConst());
try testing.expectEqual(.exact, res.setFloat(@as(Float, 0.0), .away));
try expectNormalized(0, res.toConst());
try testing.expectEqual(.exact, res.setFloat(@as(Float, 0.0), .trunc));
try expectNormalized(0, res.toConst());
try testing.expectEqual(.exact, res.setFloat(@as(Float, 0.0), .floor));
try expectNormalized(0, res.toConst());
try testing.expectEqual(.exact, res.setFloat(@as(Float, 0.0), .ceil));
try expectNormalized(0, res.toConst());
try testing.expectEqual(.inexact, res.setFloat(@as(Float, 0.25), .nearest_even));
try expectNormalized(0, res.toConst());
try testing.expectEqual(.inexact, res.setFloat(@as(Float, 0.25), .away));
try expectNormalized(1, res.toConst());
try testing.expectEqual(.inexact, res.setFloat(@as(Float, 0.25), .trunc));
try expectNormalized(0, res.toConst());
try testing.expectEqual(.inexact, res.setFloat(@as(Float, 0.25), .floor));
try expectNormalized(0, res.toConst());
try testing.expectEqual(.inexact, res.setFloat(@as(Float, 0.25), .ceil));
try expectNormalized(1, res.toConst());
try testing.expectEqual(.inexact, res.setFloat(@as(Float, 0.5), .nearest_even));
try expectNormalized(0, res.toConst());
try testing.expectEqual(.inexact, res.setFloat(@as(Float, 0.5), .away));
try expectNormalized(1, res.toConst());
try testing.expectEqual(.inexact, res.setFloat(@as(Float, 0.5), .trunc));
try expectNormalized(0, res.toConst());
try testing.expectEqual(.inexact, res.setFloat(@as(Float, 0.5), .floor));
try expectNormalized(0, res.toConst());
try testing.expectEqual(.inexact, res.setFloat(@as(Float, 0.5), .ceil));
try expectNormalized(1, res.toConst());
try testing.expectEqual(.inexact, res.setFloat(@as(Float, 0.75), .nearest_even));
try expectNormalized(1, res.toConst());
try testing.expectEqual(.inexact, res.setFloat(@as(Float, 0.75), .away));
try expectNormalized(1, res.toConst());
try testing.expectEqual(.inexact, res.setFloat(@as(Float, 0.75), .trunc));
try expectNormalized(0, res.toConst());
try testing.expectEqual(.inexact, res.setFloat(@as(Float, 0.75), .floor));
try expectNormalized(0, res.toConst());
try testing.expectEqual(.inexact, res.setFloat(@as(Float, 0.75), .ceil));
try expectNormalized(1, res.toConst());
try testing.expectEqual(.exact, res.setFloat(@as(Float, 1.0), .nearest_even));
try expectNormalized(1, res.toConst());
try testing.expectEqual(.exact, res.setFloat(@as(Float, 1.0), .away));
try expectNormalized(1, res.toConst());
try testing.expectEqual(.exact, res.setFloat(@as(Float, 1.0), .trunc));
try expectNormalized(1, res.toConst());
try testing.expectEqual(.exact, res.setFloat(@as(Float, 1.0), .floor));
try expectNormalized(1, res.toConst());
try testing.expectEqual(.exact, res.setFloat(@as(Float, 1.0), .ceil));
try expectNormalized(1, res.toConst());
try testing.expectEqual(.inexact, res.setFloat(@as(Float, 1.5), .nearest_even));
try expectNormalized(2, res.toConst());
try testing.expectEqual(.inexact, res.setFloat(@as(Float, 1.5), .away));
try expectNormalized(2, res.toConst());
try testing.expectEqual(.inexact, res.setFloat(@as(Float, 1.5), .trunc));
try expectNormalized(1, res.toConst());
try testing.expectEqual(.inexact, res.setFloat(@as(Float, 1.5), .floor));
try expectNormalized(1, res.toConst());
try testing.expectEqual(.inexact, res.setFloat(@as(Float, 1.5), .ceil));
try expectNormalized(2, res.toConst());
try testing.expectEqual(.exact, res.setFloat(@as(Float, 2.0), .nearest_even));
try expectNormalized(2, res.toConst());
try testing.expectEqual(.exact, res.setFloat(@as(Float, 2.0), .away));
try expectNormalized(2, res.toConst());
try testing.expectEqual(.exact, res.setFloat(@as(Float, 2.0), .trunc));
try expectNormalized(2, res.toConst());
try testing.expectEqual(.exact, res.setFloat(@as(Float, 2.0), .floor));
try expectNormalized(2, res.toConst());
try testing.expectEqual(.exact, res.setFloat(@as(Float, 2.0), .ceil));
try expectNormalized(2, res.toConst());
try testing.expectEqual(.exact, res.setFloat(@as(Float, 0x1p10), .nearest_even));
try expectNormalized(1 << 10, res.toConst());
try testing.expectEqual(.exact, res.setFloat(@as(Float, 0x1p10), .away));
try expectNormalized(1 << 10, res.toConst());
try testing.expectEqual(.exact, res.setFloat(@as(Float, 0x1p10), .trunc));
try expectNormalized(1 << 10, res.toConst());
try testing.expectEqual(.exact, res.setFloat(@as(Float, 0x1p10), .floor));
try expectNormalized(1 << 10, res.toConst());
try testing.expectEqual(.exact, res.setFloat(@as(Float, 0x1p10), .ceil));
try expectNormalized(1 << 10, res.toConst());
}
test setFloat {
if (builtin.zig_backend == .stage2_c) return error.SkipZigTest;
try setFloat(f16);
try setFloat(f32);
try setFloat(f64);
try setFloat(f80);
try setFloat(f128);
try setFloat(c_longdouble);
try setFloat(comptime_float);
}
fn toFloat(comptime Float: type) !void {
const Result = struct { Float, std.math.big.int.Exactness };
const fractional_bits = std.math.floatFractionalBits(Float);
var int_limbs: [
std.math.big.int.calcNonZeroTwosCompLimbCount(2 + fractional_bits)
]Limb = undefined;
var int: Mutable = .{
.limbs = &int_limbs,
.len = undefined,
.positive = undefined,
};
int.set(-(1 << (fractional_bits + 1)) - 1);
try testing.expectEqual(
Result{ comptime -std.math.ldexp(@as(Float, 1), fractional_bits + 1), .inexact },
int.toFloat(Float, .nearest_even),
);
try testing.expectEqual(
Result{ comptime std.math.nextAfter(
Float,
-std.math.ldexp(@as(Float, 1), fractional_bits + 1),
-std.math.inf(Float),
), .inexact },
int.toFloat(Float, .away),
);
try testing.expectEqual(
Result{ comptime -std.math.ldexp(@as(Float, 1), fractional_bits + 1), .inexact },
int.toFloat(Float, .trunc),
);
try testing.expectEqual(
Result{ comptime std.math.nextAfter(
Float,
-std.math.ldexp(@as(Float, 1), fractional_bits + 1),
-std.math.inf(Float),
), .inexact },
int.toFloat(Float, .floor),
);
try testing.expectEqual(
Result{ comptime -std.math.ldexp(@as(Float, 1), fractional_bits + 1), .inexact },
int.toFloat(Float, .ceil),
);
int.set(-1 << (fractional_bits + 1));
try testing.expectEqual(
Result{ comptime -std.math.ldexp(@as(Float, 1), fractional_bits + 1), .exact },
int.toFloat(Float, .nearest_even),
);
try testing.expectEqual(
Result{ comptime -std.math.ldexp(@as(Float, 1), fractional_bits + 1), .exact },
int.toFloat(Float, .away),
);
try testing.expectEqual(
Result{ comptime -std.math.ldexp(@as(Float, 1), fractional_bits + 1), .exact },
int.toFloat(Float, .trunc),
);
try testing.expectEqual(
Result{ comptime -std.math.ldexp(@as(Float, 1), fractional_bits + 1), .exact },
int.toFloat(Float, .floor),
);
try testing.expectEqual(
Result{ comptime -std.math.ldexp(@as(Float, 1), fractional_bits + 1), .exact },
int.toFloat(Float, .ceil),
);
int.set(-(1 << (fractional_bits + 1)) + 1);
try testing.expectEqual(
Result{ comptime -std.math.ldexp(@as(Float, 1), fractional_bits + 1) + 1.0, .exact },
int.toFloat(Float, .nearest_even),
);
try testing.expectEqual(
Result{ comptime -std.math.ldexp(@as(Float, 1), fractional_bits + 1) + 1.0, .exact },
int.toFloat(Float, .away),
);
try testing.expectEqual(
Result{ comptime -std.math.ldexp(@as(Float, 1), fractional_bits + 1) + 1.0, .exact },
int.toFloat(Float, .trunc),
);
try testing.expectEqual(
Result{ comptime -std.math.ldexp(@as(Float, 1), fractional_bits + 1) + 1.0, .exact },
int.toFloat(Float, .floor),
);
try testing.expectEqual(
Result{ comptime -std.math.ldexp(@as(Float, 1), fractional_bits + 1) + 1.0, .exact },
int.toFloat(Float, .ceil),
);
int.set(-1 << 10);
try testing.expectEqual(Result{ -0x1p10, .exact }, int.toFloat(Float, .nearest_even));
try testing.expectEqual(Result{ -0x1p10, .exact }, int.toFloat(Float, .away));
try testing.expectEqual(Result{ -0x1p10, .exact }, int.toFloat(Float, .trunc));
try testing.expectEqual(Result{ -0x1p10, .exact }, int.toFloat(Float, .floor));
try testing.expectEqual(Result{ -0x1p10, .exact }, int.toFloat(Float, .ceil));
int.set(-1);
try testing.expectEqual(Result{ -1.0, .exact }, int.toFloat(Float, .nearest_even));
try testing.expectEqual(Result{ -1.0, .exact }, int.toFloat(Float, .away));
try testing.expectEqual(Result{ -1.0, .exact }, int.toFloat(Float, .trunc));
try testing.expectEqual(Result{ -1.0, .exact }, int.toFloat(Float, .floor));
try testing.expectEqual(Result{ -1.0, .exact }, int.toFloat(Float, .ceil));
int.set(0);
try testing.expectEqual(Result{ 0.0, .exact }, int.toFloat(Float, .nearest_even));
try testing.expectEqual(Result{ 0.0, .exact }, int.toFloat(Float, .away));
try testing.expectEqual(Result{ 0.0, .exact }, int.toFloat(Float, .trunc));
try testing.expectEqual(Result{ 0.0, .exact }, int.toFloat(Float, .floor));
try testing.expectEqual(Result{ 0.0, .exact }, int.toFloat(Float, .ceil));
int.set(1);
try testing.expectEqual(Result{ 1.0, .exact }, int.toFloat(Float, .nearest_even));
try testing.expectEqual(Result{ 1.0, .exact }, int.toFloat(Float, .away));
try testing.expectEqual(Result{ 1.0, .exact }, int.toFloat(Float, .trunc));
try testing.expectEqual(Result{ 1.0, .exact }, int.toFloat(Float, .floor));
try testing.expectEqual(Result{ 1.0, .exact }, int.toFloat(Float, .ceil));
int.set(1 << 10);
try testing.expectEqual(Result{ 0x1p10, .exact }, int.toFloat(Float, .nearest_even));
try testing.expectEqual(Result{ 0x1p10, .exact }, int.toFloat(Float, .away));
try testing.expectEqual(Result{ 0x1p10, .exact }, int.toFloat(Float, .trunc));
try testing.expectEqual(Result{ 0x1p10, .exact }, int.toFloat(Float, .floor));
try testing.expectEqual(Result{ 0x1p10, .exact }, int.toFloat(Float, .ceil));
int.set((1 << (fractional_bits + 1)) - 1);
try testing.expectEqual(
Result{ comptime std.math.ldexp(@as(Float, 1), fractional_bits + 1) - 1.0, .exact },
int.toFloat(Float, .nearest_even),
);
try testing.expectEqual(
Result{ comptime std.math.ldexp(@as(Float, 1), fractional_bits + 1) - 1.0, .exact },
int.toFloat(Float, .away),
);
try testing.expectEqual(
Result{ comptime std.math.ldexp(@as(Float, 1), fractional_bits + 1) - 1.0, .exact },
int.toFloat(Float, .trunc),
);
try testing.expectEqual(
Result{ comptime std.math.ldexp(@as(Float, 1), fractional_bits + 1) - 1.0, .exact },
int.toFloat(Float, .floor),
);
try testing.expectEqual(
Result{ comptime std.math.ldexp(@as(Float, 1), fractional_bits + 1) - 1.0, .exact },
int.toFloat(Float, .ceil),
);
int.set(1 << (fractional_bits + 1));
try testing.expectEqual(
Result{ comptime std.math.ldexp(@as(Float, 1), fractional_bits + 1), .exact },
int.toFloat(Float, .nearest_even),
);
try testing.expectEqual(
Result{ comptime std.math.ldexp(@as(Float, 1), fractional_bits + 1), .exact },
int.toFloat(Float, .away),
);
try testing.expectEqual(
Result{ comptime std.math.ldexp(@as(Float, 1), fractional_bits + 1), .exact },
int.toFloat(Float, .trunc),
);
try testing.expectEqual(
Result{ comptime std.math.ldexp(@as(Float, 1), fractional_bits + 1), .exact },
int.toFloat(Float, .floor),
);
try testing.expectEqual(
Result{ comptime std.math.ldexp(@as(Float, 1), fractional_bits + 1), .exact },
int.toFloat(Float, .ceil),
);
int.set((1 << (fractional_bits + 1)) + 1);
try testing.expectEqual(
Result{ comptime std.math.ldexp(@as(Float, 1), fractional_bits + 1), .inexact },
int.toFloat(Float, .nearest_even),
);
try testing.expectEqual(
Result{ comptime std.math.nextAfter(
Float,
std.math.ldexp(@as(Float, 1), fractional_bits + 1),
std.math.inf(Float),
), .inexact },
int.toFloat(Float, .away),
);
try testing.expectEqual(
Result{ comptime std.math.ldexp(@as(Float, 1), fractional_bits + 1), .inexact },
int.toFloat(Float, .trunc),
);
try testing.expectEqual(
Result{ comptime std.math.ldexp(@as(Float, 1), fractional_bits + 1), .inexact },
int.toFloat(Float, .floor),
);
try testing.expectEqual(
Result{ comptime std.math.nextAfter(
Float,
std.math.ldexp(@as(Float, 1), fractional_bits + 1),
std.math.inf(Float),
), .inexact },
int.toFloat(Float, .ceil),
);
}
test toFloat {
if (builtin.zig_backend == .stage2_llvm) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/24191
try toFloat(f16);
try toFloat(f32);
try toFloat(f64);
try toFloat(f80);
try toFloat(f128);
try toFloat(c_longdouble);
}
test "normalize" { test "normalize" {
var a = try Managed.init(testing.allocator); var a = try Managed.init(testing.allocator);
defer a.deinit(); defer a.deinit();

View File

@ -1,820 +0,0 @@
const std = @import("../../std.zig");
const builtin = @import("builtin");
const debug = std.debug;
const math = std.math;
const mem = std.mem;
const testing = std.testing;
const Allocator = mem.Allocator;
const Limb = std.math.big.Limb;
const DoubleLimb = std.math.big.DoubleLimb;
const Int = std.math.big.int.Managed;
const IntConst = std.math.big.int.Const;
/// An arbitrary-precision rational number.
///
/// Memory is allocated as needed for operations to ensure full precision is kept. The precision
/// of a Rational is only bounded by memory.
///
/// Rational's are always normalized. That is, for a Rational r = p/q where p and q are integers,
/// gcd(p, q) = 1 always.
///
/// TODO rework this to store its own allocator and use a non-managed big int, to avoid double
/// allocator storage.
pub const Rational = struct {
/// Numerator. Determines the sign of the Rational.
p: Int,
/// Denominator. Sign is ignored.
q: Int,
/// Create a new Rational. A small amount of memory will be allocated on initialization.
/// This will be 2 * Int.default_capacity.
pub fn init(a: Allocator) !Rational {
var p = try Int.init(a);
errdefer p.deinit();
return Rational{
.p = p,
.q = try Int.initSet(a, 1),
};
}
/// Frees all memory associated with a Rational.
pub fn deinit(self: *Rational) void {
self.p.deinit();
self.q.deinit();
}
/// Set a Rational from a primitive integer type.
pub fn setInt(self: *Rational, a: anytype) !void {
try self.p.set(a);
try self.q.set(1);
}
/// Set a Rational from a string of the form `A/B` where A and B are base-10 integers.
pub fn setFloatString(self: *Rational, str: []const u8) !void {
// TODO: Accept a/b fractions and exponent form
if (str.len == 0) {
return error.InvalidFloatString;
}
const State = enum {
Integer,
Fractional,
};
var state = State.Integer;
var point: ?usize = null;
var start: usize = 0;
if (str[0] == '-') {
start += 1;
}
for (str, 0..) |c, i| {
switch (state) {
State.Integer => {
switch (c) {
'.' => {
state = State.Fractional;
point = i;
},
'0'...'9' => {
// okay
},
else => {
return error.InvalidFloatString;
},
}
},
State.Fractional => {
switch (c) {
'0'...'9' => {
// okay
},
else => {
return error.InvalidFloatString;
},
}
},
}
}
// TODO: batch the multiplies by 10
if (point) |i| {
try self.p.setString(10, str[0..i]);
const base = IntConst{ .limbs = &[_]Limb{10}, .positive = true };
var local_buf: [@sizeOf(Limb) * Int.default_capacity]u8 align(@alignOf(Limb)) = undefined;
var fba = std.heap.FixedBufferAllocator.init(&local_buf);
const base_managed = try base.toManaged(fba.allocator());
var j: usize = start;
while (j < str.len - i - 1) : (j += 1) {
try self.p.ensureMulCapacity(self.p.toConst(), base);
try self.p.mul(&self.p, &base_managed);
}
try self.q.setString(10, str[i + 1 ..]);
try self.p.add(&self.p, &self.q);
try self.q.set(1);
var k: usize = i + 1;
while (k < str.len) : (k += 1) {
try self.q.mul(&self.q, &base_managed);
}
try self.reduce();
} else {
try self.p.setString(10, str[0..]);
try self.q.set(1);
}
}
/// Set a Rational from a floating-point value. The rational will have enough precision to
/// completely represent the provided float.
pub fn setFloat(self: *Rational, comptime T: type, f: T) !void {
// Translated from golang.go/src/math/big/rat.go.
debug.assert(@typeInfo(T) == .float);
const UnsignedInt = std.meta.Int(.unsigned, @typeInfo(T).float.bits);
const f_bits = @as(UnsignedInt, @bitCast(f));
const exponent_bits = math.floatExponentBits(T);
const exponent_bias = (1 << (exponent_bits - 1)) - 1;
const mantissa_bits = math.floatMantissaBits(T);
const exponent_mask = (1 << exponent_bits) - 1;
const mantissa_mask = (1 << mantissa_bits) - 1;
var exponent = @as(i16, @intCast((f_bits >> mantissa_bits) & exponent_mask));
var mantissa = f_bits & mantissa_mask;
switch (exponent) {
exponent_mask => {
return error.NonFiniteFloat;
},
0 => {
// denormal
exponent -= exponent_bias - 1;
},
else => {
// normal
mantissa |= 1 << mantissa_bits;
exponent -= exponent_bias;
},
}
var shift: i16 = mantissa_bits - exponent;
// factor out powers of two early from rational
while (mantissa & 1 == 0 and shift > 0) {
mantissa >>= 1;
shift -= 1;
}
try self.p.set(mantissa);
self.p.setSign(f >= 0);
try self.q.set(1);
if (shift >= 0) {
try self.q.shiftLeft(&self.q, @as(usize, @intCast(shift)));
} else {
try self.p.shiftLeft(&self.p, @as(usize, @intCast(-shift)));
}
try self.reduce();
}
/// Return a floating-point value that is the closest value to a Rational.
///
/// The result may not be exact if the Rational is too precise or too large for the
/// target type.
pub fn toFloat(self: Rational, comptime T: type) !T {
// Translated from golang.go/src/math/big/rat.go.
// TODO: Indicate whether the result is not exact.
debug.assert(@typeInfo(T) == .float);
const fsize = @typeInfo(T).float.bits;
const BitReprType = std.meta.Int(.unsigned, fsize);
const msize = math.floatMantissaBits(T);
const msize1 = msize + 1;
const msize2 = msize1 + 1;
const esize = math.floatExponentBits(T);
const ebias = (1 << (esize - 1)) - 1;
const emin = 1 - ebias;
if (self.p.eqlZero()) {
return 0;
}
// 1. left-shift a or sub so that a/b is in [1 << msize1, 1 << (msize2 + 1)]
var exp = @as(isize, @intCast(self.p.bitCountTwosComp())) - @as(isize, @intCast(self.q.bitCountTwosComp()));
var a2 = try self.p.clone();
defer a2.deinit();
var b2 = try self.q.clone();
defer b2.deinit();
const shift = msize2 - exp;
if (shift >= 0) {
try a2.shiftLeft(&a2, @as(usize, @intCast(shift)));
} else {
try b2.shiftLeft(&b2, @as(usize, @intCast(-shift)));
}
// 2. compute quotient and remainder
var q = try Int.init(self.p.allocator);
defer q.deinit();
// unused
var r = try Int.init(self.p.allocator);
defer r.deinit();
try Int.divTrunc(&q, &r, &a2, &b2);
var mantissa = extractLowBits(q, BitReprType);
var have_rem = r.len() > 0;
// 3. q didn't fit in msize2 bits, redo division b2 << 1
if (mantissa >> msize2 == 1) {
if (mantissa & 1 == 1) {
have_rem = true;
}
mantissa >>= 1;
exp += 1;
}
if (mantissa >> msize1 != 1) {
// NOTE: This can be hit if the limb size is small (u8/16).
@panic("unexpected bits in result");
}
// 4. Rounding
if (emin - msize <= exp and exp <= emin) {
// denormal
const shift1 = @as(math.Log2Int(BitReprType), @intCast(emin - (exp - 1)));
const lost_bits = mantissa & ((@as(BitReprType, @intCast(1)) << shift1) - 1);
have_rem = have_rem or lost_bits != 0;
mantissa >>= shift1;
exp = 2 - ebias;
}
// round q using round-half-to-even
var exact = !have_rem;
if (mantissa & 1 != 0) {
exact = false;
if (have_rem or (mantissa & 2 != 0)) {
mantissa += 1;
if (mantissa >= 1 << msize2) {
// 11...1 => 100...0
mantissa >>= 1;
exp += 1;
}
}
}
mantissa >>= 1;
const f = math.scalbn(@as(T, @floatFromInt(mantissa)), @as(i32, @intCast(exp - msize1)));
if (math.isInf(f)) {
exact = false;
}
return if (self.p.isPositive()) f else -f;
}
/// Set a rational from an integer ratio.
pub fn setRatio(self: *Rational, p: anytype, q: anytype) !void {
try self.p.set(p);
try self.q.set(q);
self.p.setSign(@intFromBool(self.p.isPositive()) ^ @intFromBool(self.q.isPositive()) == 0);
self.q.setSign(true);
try self.reduce();
if (self.q.eqlZero()) {
@panic("cannot set rational with denominator = 0");
}
}
/// Set a Rational directly from an Int.
pub fn copyInt(self: *Rational, a: Int) !void {
try self.p.copy(a.toConst());
try self.q.set(1);
}
/// Set a Rational directly from a ratio of two Int's.
pub fn copyRatio(self: *Rational, a: Int, b: Int) !void {
try self.p.copy(a.toConst());
try self.q.copy(b.toConst());
self.p.setSign(@intFromBool(self.p.isPositive()) ^ @intFromBool(self.q.isPositive()) == 0);
self.q.setSign(true);
try self.reduce();
}
/// Make a Rational positive.
pub fn abs(r: *Rational) void {
r.p.abs();
}
/// Negate the sign of a Rational.
pub fn negate(r: *Rational) void {
r.p.negate();
}
/// Efficiently swap a Rational with another. This swaps the limb pointers and a full copy is not
/// performed. The address of the limbs field will not be the same after this function.
pub fn swap(r: *Rational, other: *Rational) void {
r.p.swap(&other.p);
r.q.swap(&other.q);
}
/// Returns math.Order.lt, math.Order.eq, math.Order.gt if a < b, a == b or
/// a > b respectively.
pub fn order(a: Rational, b: Rational) !math.Order {
return cmpInternal(a, b, false);
}
/// Returns math.Order.lt, math.Order.eq, math.Order.gt if |a| < |b|, |a| ==
/// |b| or |a| > |b| respectively.
pub fn orderAbs(a: Rational, b: Rational) !math.Order {
return cmpInternal(a, b, true);
}
// p/q > x/y iff p*y > x*q
fn cmpInternal(a: Rational, b: Rational, is_abs: bool) !math.Order {
// TODO: Would a div compare algorithm of sorts be viable and quicker? Can we avoid
// the memory allocations here?
var q = try Int.init(a.p.allocator);
defer q.deinit();
var p = try Int.init(b.p.allocator);
defer p.deinit();
try q.mul(&a.p, &b.q);
try p.mul(&b.p, &a.q);
return if (is_abs) q.orderAbs(p) else q.order(p);
}
/// rma = a + b.
///
/// rma, a and b may be aliases. However, it is more efficient if rma does not alias a or b.
///
/// Returns an error if memory could not be allocated.
pub fn add(rma: *Rational, a: Rational, b: Rational) !void {
var r = rma;
var aliased = rma.p.limbs.ptr == a.p.limbs.ptr or rma.p.limbs.ptr == b.p.limbs.ptr;
var sr: Rational = undefined;
if (aliased) {
sr = try Rational.init(rma.p.allocator);
r = &sr;
aliased = true;
}
defer if (aliased) {
rma.swap(r);
r.deinit();
};
try r.p.mul(&a.p, &b.q);
try r.q.mul(&b.p, &a.q);
try r.p.add(&r.p, &r.q);
try r.q.mul(&a.q, &b.q);
try r.reduce();
}
/// rma = a - b.
///
/// rma, a and b may be aliases. However, it is more efficient if rma does not alias a or b.
///
/// Returns an error if memory could not be allocated.
pub fn sub(rma: *Rational, a: Rational, b: Rational) !void {
var r = rma;
var aliased = rma.p.limbs.ptr == a.p.limbs.ptr or rma.p.limbs.ptr == b.p.limbs.ptr;
var sr: Rational = undefined;
if (aliased) {
sr = try Rational.init(rma.p.allocator);
r = &sr;
aliased = true;
}
defer if (aliased) {
rma.swap(r);
r.deinit();
};
try r.p.mul(&a.p, &b.q);
try r.q.mul(&b.p, &a.q);
try r.p.sub(&r.p, &r.q);
try r.q.mul(&a.q, &b.q);
try r.reduce();
}
/// rma = a * b.
///
/// rma, a and b may be aliases. However, it is more efficient if rma does not alias a or b.
///
/// Returns an error if memory could not be allocated.
pub fn mul(r: *Rational, a: Rational, b: Rational) !void {
try r.p.mul(&a.p, &b.p);
try r.q.mul(&a.q, &b.q);
try r.reduce();
}
/// rma = a / b.
///
/// rma, a and b may be aliases. However, it is more efficient if rma does not alias a or b.
///
/// Returns an error if memory could not be allocated.
pub fn div(r: *Rational, a: Rational, b: Rational) !void {
if (b.p.eqlZero()) {
@panic("division by zero");
}
try r.p.mul(&a.p, &b.q);
try r.q.mul(&b.p, &a.q);
try r.reduce();
}
/// Invert the numerator and denominator fields of a Rational. p/q => q/p.
pub fn invert(r: *Rational) void {
Int.swap(&r.p, &r.q);
}
// reduce r/q such that gcd(r, q) = 1
fn reduce(r: *Rational) !void {
var a = try Int.init(r.p.allocator);
defer a.deinit();
const sign = r.p.isPositive();
r.p.abs();
try a.gcd(&r.p, &r.q);
r.p.setSign(sign);
const one = IntConst{ .limbs = &[_]Limb{1}, .positive = true };
if (a.toConst().order(one) != .eq) {
var unused = try Int.init(r.p.allocator);
defer unused.deinit();
// TODO: divexact would be useful here
// TODO: don't copy r.q for div
try Int.divTrunc(&r.p, &unused, &r.p, &a);
try Int.divTrunc(&r.q, &unused, &r.q, &a);
}
}
};
fn extractLowBits(a: Int, comptime T: type) T {
debug.assert(@typeInfo(T) == .int);
const t_bits = @typeInfo(T).int.bits;
const limb_bits = @typeInfo(Limb).int.bits;
if (t_bits <= limb_bits) {
return @as(T, @truncate(a.limbs[0]));
} else {
var r: T = 0;
comptime var i: usize = 0;
// Remainder is always 0 since if t_bits >= limb_bits -> Limb | T and both
// are powers of two.
inline while (i < t_bits / limb_bits) : (i += 1) {
r |= math.shl(T, a.limbs[i], i * limb_bits);
}
return r;
}
}
test extractLowBits {
var a = try Int.initSet(testing.allocator, 0x11112222333344441234567887654321);
defer a.deinit();
const a1 = extractLowBits(a, u8);
try testing.expect(a1 == 0x21);
const a2 = extractLowBits(a, u16);
try testing.expect(a2 == 0x4321);
const a3 = extractLowBits(a, u32);
try testing.expect(a3 == 0x87654321);
const a4 = extractLowBits(a, u64);
try testing.expect(a4 == 0x1234567887654321);
const a5 = extractLowBits(a, u128);
try testing.expect(a5 == 0x11112222333344441234567887654321);
}
test "set" {
var a = try Rational.init(testing.allocator);
defer a.deinit();
try a.setInt(5);
try testing.expect((try a.p.toInt(u32)) == 5);
try testing.expect((try a.q.toInt(u32)) == 1);
try a.setRatio(7, 3);
try testing.expect((try a.p.toInt(u32)) == 7);
try testing.expect((try a.q.toInt(u32)) == 3);
try a.setRatio(9, 3);
try testing.expect((try a.p.toInt(i32)) == 3);
try testing.expect((try a.q.toInt(i32)) == 1);
try a.setRatio(-9, 3);
try testing.expect((try a.p.toInt(i32)) == -3);
try testing.expect((try a.q.toInt(i32)) == 1);
try a.setRatio(9, -3);
try testing.expect((try a.p.toInt(i32)) == -3);
try testing.expect((try a.q.toInt(i32)) == 1);
try a.setRatio(-9, -3);
try testing.expect((try a.p.toInt(i32)) == 3);
try testing.expect((try a.q.toInt(i32)) == 1);
}
test "setFloat" {
var a = try Rational.init(testing.allocator);
defer a.deinit();
try a.setFloat(f64, 2.5);
try testing.expect((try a.p.toInt(i32)) == 5);
try testing.expect((try a.q.toInt(i32)) == 2);
try a.setFloat(f32, -2.5);
try testing.expect((try a.p.toInt(i32)) == -5);
try testing.expect((try a.q.toInt(i32)) == 2);
try a.setFloat(f32, 3.141593);
// = 3.14159297943115234375
try testing.expect((try a.p.toInt(u32)) == 3294199);
try testing.expect((try a.q.toInt(u32)) == 1048576);
try a.setFloat(f64, 72.141593120712409172417410926841290461290467124);
// = 72.1415931207124145885245525278151035308837890625
try testing.expect((try a.p.toInt(u128)) == 5076513310880537);
try testing.expect((try a.q.toInt(u128)) == 70368744177664);
}
test "setFloatString" {
var a = try Rational.init(testing.allocator);
defer a.deinit();
try a.setFloatString("72.14159312071241458852455252781510353");
// = 72.1415931207124145885245525278151035308837890625
try testing.expect((try a.p.toInt(u128)) == 7214159312071241458852455252781510353);
try testing.expect((try a.q.toInt(u128)) == 100000000000000000000000000000000000);
}
test "toFloat" {
var a = try Rational.init(testing.allocator);
defer a.deinit();
// = 3.14159297943115234375
try a.setRatio(3294199, 1048576);
try testing.expect((try a.toFloat(f64)) == 3.14159297943115234375);
// = 72.1415931207124145885245525278151035308837890625
try a.setRatio(5076513310880537, 70368744177664);
try testing.expect((try a.toFloat(f64)) == 72.141593120712409172417410926841290461290467124);
}
test "set/to Float round-trip" {
var a = try Rational.init(testing.allocator);
defer a.deinit();
var prng = std.Random.DefaultPrng.init(std.testing.random_seed);
const random = prng.random();
var i: usize = 0;
while (i < 512) : (i += 1) {
const r = random.float(f64);
try a.setFloat(f64, r);
try testing.expect((try a.toFloat(f64)) == r);
}
}
test "copy" {
var a = try Rational.init(testing.allocator);
defer a.deinit();
var b = try Int.initSet(testing.allocator, 5);
defer b.deinit();
try a.copyInt(b);
try testing.expect((try a.p.toInt(u32)) == 5);
try testing.expect((try a.q.toInt(u32)) == 1);
var c = try Int.initSet(testing.allocator, 7);
defer c.deinit();
var d = try Int.initSet(testing.allocator, 3);
defer d.deinit();
try a.copyRatio(c, d);
try testing.expect((try a.p.toInt(u32)) == 7);
try testing.expect((try a.q.toInt(u32)) == 3);
var e = try Int.initSet(testing.allocator, 9);
defer e.deinit();
var f = try Int.initSet(testing.allocator, 3);
defer f.deinit();
try a.copyRatio(e, f);
try testing.expect((try a.p.toInt(u32)) == 3);
try testing.expect((try a.q.toInt(u32)) == 1);
}
test "negate" {
var a = try Rational.init(testing.allocator);
defer a.deinit();
try a.setInt(-50);
try testing.expect((try a.p.toInt(i32)) == -50);
try testing.expect((try a.q.toInt(i32)) == 1);
a.negate();
try testing.expect((try a.p.toInt(i32)) == 50);
try testing.expect((try a.q.toInt(i32)) == 1);
a.negate();
try testing.expect((try a.p.toInt(i32)) == -50);
try testing.expect((try a.q.toInt(i32)) == 1);
}
test "abs" {
var a = try Rational.init(testing.allocator);
defer a.deinit();
try a.setInt(-50);
try testing.expect((try a.p.toInt(i32)) == -50);
try testing.expect((try a.q.toInt(i32)) == 1);
a.abs();
try testing.expect((try a.p.toInt(i32)) == 50);
try testing.expect((try a.q.toInt(i32)) == 1);
a.abs();
try testing.expect((try a.p.toInt(i32)) == 50);
try testing.expect((try a.q.toInt(i32)) == 1);
}
test "swap" {
var a = try Rational.init(testing.allocator);
defer a.deinit();
var b = try Rational.init(testing.allocator);
defer b.deinit();
try a.setRatio(50, 23);
try b.setRatio(17, 3);
try testing.expect((try a.p.toInt(u32)) == 50);
try testing.expect((try a.q.toInt(u32)) == 23);
try testing.expect((try b.p.toInt(u32)) == 17);
try testing.expect((try b.q.toInt(u32)) == 3);
a.swap(&b);
try testing.expect((try a.p.toInt(u32)) == 17);
try testing.expect((try a.q.toInt(u32)) == 3);
try testing.expect((try b.p.toInt(u32)) == 50);
try testing.expect((try b.q.toInt(u32)) == 23);
}
test "order" {
var a = try Rational.init(testing.allocator);
defer a.deinit();
var b = try Rational.init(testing.allocator);
defer b.deinit();
try a.setRatio(500, 231);
try b.setRatio(18903, 8584);
try testing.expect((try a.order(b)) == .lt);
try a.setRatio(890, 10);
try b.setRatio(89, 1);
try testing.expect((try a.order(b)) == .eq);
}
test "order/orderAbs with negative" {
var a = try Rational.init(testing.allocator);
defer a.deinit();
var b = try Rational.init(testing.allocator);
defer b.deinit();
try a.setRatio(1, 1);
try b.setRatio(-2, 1);
try testing.expect((try a.order(b)) == .gt);
try testing.expect((try a.orderAbs(b)) == .lt);
}
test "add single-limb" {
var a = try Rational.init(testing.allocator);
defer a.deinit();
var b = try Rational.init(testing.allocator);
defer b.deinit();
try a.setRatio(500, 231);
try b.setRatio(18903, 8584);
try testing.expect((try a.order(b)) == .lt);
try a.setRatio(890, 10);
try b.setRatio(89, 1);
try testing.expect((try a.order(b)) == .eq);
}
test "add" {
var a = try Rational.init(testing.allocator);
defer a.deinit();
var b = try Rational.init(testing.allocator);
defer b.deinit();
var r = try Rational.init(testing.allocator);
defer r.deinit();
try a.setRatio(78923, 23341);
try b.setRatio(123097, 12441414);
try a.add(a, b);
try r.setRatio(984786924199, 290395044174);
try testing.expect((try a.order(r)) == .eq);
}
test "sub" {
var a = try Rational.init(testing.allocator);
defer a.deinit();
var b = try Rational.init(testing.allocator);
defer b.deinit();
var r = try Rational.init(testing.allocator);
defer r.deinit();
try a.setRatio(78923, 23341);
try b.setRatio(123097, 12441414);
try a.sub(a, b);
try r.setRatio(979040510045, 290395044174);
try testing.expect((try a.order(r)) == .eq);
}
test "mul" {
var a = try Rational.init(testing.allocator);
defer a.deinit();
var b = try Rational.init(testing.allocator);
defer b.deinit();
var r = try Rational.init(testing.allocator);
defer r.deinit();
try a.setRatio(78923, 23341);
try b.setRatio(123097, 12441414);
try a.mul(a, b);
try r.setRatio(571481443, 17082061422);
try testing.expect((try a.order(r)) == .eq);
}
test "div" {
{
var a = try Rational.init(testing.allocator);
defer a.deinit();
var b = try Rational.init(testing.allocator);
defer b.deinit();
var r = try Rational.init(testing.allocator);
defer r.deinit();
try a.setRatio(78923, 23341);
try b.setRatio(123097, 12441414);
try a.div(a, b);
try r.setRatio(75531824394, 221015929);
try testing.expect((try a.order(r)) == .eq);
}
{
var a = try Rational.init(testing.allocator);
defer a.deinit();
var r = try Rational.init(testing.allocator);
defer r.deinit();
try a.setRatio(78923, 23341);
a.invert();
try r.setRatio(23341, 78923);
try testing.expect((try a.order(r)) == .eq);
try a.setRatio(-78923, 23341);
a.invert();
try r.setRatio(-23341, 78923);
try testing.expect((try a.order(r)) == .eq);
}
}

View File

@ -4,6 +4,119 @@ const assert = std.debug.assert;
const expect = std.testing.expect; const expect = std.testing.expect;
const expectEqual = std.testing.expectEqual; const expectEqual = std.testing.expectEqual;
pub const Sign = enum(u1) { positive, negative };
pub fn FloatRepr(comptime Float: type) type {
const fractional_bits = floatFractionalBits(Float);
const exponent_bits = floatExponentBits(Float);
return packed struct {
const Repr = @This();
mantissa: StoredMantissa,
exponent: BiasedExponent,
sign: Sign,
pub const StoredMantissa = @Type(.{ .int = .{
.signedness = .unsigned,
.bits = floatMantissaBits(Float),
} });
pub const Mantissa = @Type(.{ .int = .{
.signedness = .unsigned,
.bits = 1 + fractional_bits,
} });
pub const Exponent = @Type(.{ .int = .{
.signedness = .signed,
.bits = exponent_bits,
} });
pub const BiasedExponent = enum(@Type(.{ .int = .{
.signedness = .unsigned,
.bits = exponent_bits,
} })) {
denormal = 0,
min_normal = 1,
zero = (1 << (exponent_bits - 1)) - 1,
max_normal = (1 << exponent_bits) - 2,
infinite = (1 << exponent_bits) - 1,
_,
pub const Int = @typeInfo(BiasedExponent).@"enum".tag_type;
pub fn unbias(biased: BiasedExponent) Exponent {
switch (biased) {
.denormal => unreachable,
else => return @bitCast(@intFromEnum(biased) -% @intFromEnum(BiasedExponent.zero)),
.infinite => unreachable,
}
}
pub fn bias(unbiased: Exponent) BiasedExponent {
return @enumFromInt(@intFromEnum(BiasedExponent.zero) +% @as(Int, @bitCast(unbiased)));
}
};
pub const Normalized = struct {
fraction: Fraction,
exponent: Normalized.Exponent,
pub const Fraction = @Type(.{ .int = .{
.signedness = .unsigned,
.bits = fractional_bits,
} });
pub const Exponent = @Type(.{ .int = .{
.signedness = .signed,
.bits = 1 + exponent_bits,
} });
/// This currently truncates denormal values, which needs to be fixed before this can be used to
/// produce a rounded value.
pub fn reconstruct(normalized: Normalized, sign: Sign) Float {
if (normalized.exponent > BiasedExponent.max_normal.unbias()) return @bitCast(Repr{
.mantissa = 0,
.exponent = .infinite,
.sign = sign,
});
const mantissa = @as(Mantissa, 1 << fractional_bits) | normalized.fraction;
if (normalized.exponent < BiasedExponent.min_normal.unbias()) return @bitCast(Repr{
.mantissa = @truncate(std.math.shr(
Mantissa,
mantissa,
BiasedExponent.min_normal.unbias() - normalized.exponent,
)),
.exponent = .denormal,
.sign = sign,
});
return @bitCast(Repr{
.mantissa = @truncate(mantissa),
.exponent = .bias(@intCast(normalized.exponent)),
.sign = sign,
});
}
};
pub const Classified = union(enum) { normalized: Normalized, infinity, nan, invalid };
fn classify(repr: Repr) Classified {
return switch (repr.exponent) {
.denormal => {
const mantissa: Mantissa = repr.mantissa;
const shift = @clz(mantissa);
return .{ .normalized = .{
.fraction = @truncate(mantissa << shift),
.exponent = @as(Normalized.Exponent, comptime BiasedExponent.min_normal.unbias()) - shift,
} };
},
else => if (repr.mantissa <= std.math.maxInt(Normalized.Fraction)) .{ .normalized = .{
.fraction = @intCast(repr.mantissa),
.exponent = repr.exponent.unbias(),
} } else .invalid,
.infinite => switch (repr.mantissa) {
0 => .infinity,
else => .nan,
},
};
}
};
}
/// Creates a raw "1.0" mantissa for floating point type T. Used to dedupe f80 logic. /// Creates a raw "1.0" mantissa for floating point type T. Used to dedupe f80 logic.
inline fn mantissaOne(comptime T: type) comptime_int { inline fn mantissaOne(comptime T: type) comptime_int {
return if (@typeInfo(T).float.bits == 80) 1 << floatFractionalBits(T) else 0; return if (@typeInfo(T).float.bits == 80) 1 << floatFractionalBits(T) else 0;

View File

@ -593,7 +593,7 @@ const Parser = struct {
switch (node.get(self.zoir)) { switch (node.get(self.zoir)) {
.int_literal => |int| switch (int) { .int_literal => |int| switch (int) {
.small => |val| return @floatFromInt(val), .small => |val| return @floatFromInt(val),
.big => |val| return val.toFloat(T), .big => |val| return val.toFloat(T, .nearest_even)[0],
}, },
.float_literal => |val| return @floatCast(val), .float_literal => |val| return @floatCast(val),
.pos_inf => return std.math.inf(T), .pos_inf => return std.math.inf(T),

View File

@ -683,6 +683,10 @@ pub const Inst = struct {
int_from_float, int_from_float,
/// Same as `int_from_float` with optimized float mode. /// Same as `int_from_float` with optimized float mode.
int_from_float_optimized, int_from_float_optimized,
/// Same as `int_from_float`, but with a safety check that the operand is in bounds.
int_from_float_safe,
/// Same as `int_from_float_optimized`, but with a safety check that the operand is in bounds.
int_from_float_optimized_safe,
/// Given an integer operand, return the float with the closest mathematical meaning. /// Given an integer operand, return the float with the closest mathematical meaning.
/// Uses the `ty_op` field. /// Uses the `ty_op` field.
float_from_int, float_from_int,
@ -1612,6 +1616,8 @@ pub fn typeOfIndex(air: *const Air, inst: Air.Inst.Index, ip: *const InternPool)
.array_to_slice, .array_to_slice,
.int_from_float, .int_from_float,
.int_from_float_optimized, .int_from_float_optimized,
.int_from_float_safe,
.int_from_float_optimized_safe,
.float_from_int, .float_from_int,
.splat, .splat,
.get_union_tag, .get_union_tag,
@ -1842,6 +1848,8 @@ pub fn mustLower(air: Air, inst: Air.Inst.Index, ip: *const InternPool) bool {
.sub_safe, .sub_safe,
.mul_safe, .mul_safe,
.intcast_safe, .intcast_safe,
.int_from_float_safe,
.int_from_float_optimized_safe,
=> true, => true,
.add, .add,

View File

@ -1,7 +1,36 @@
pt: Zcu.PerThread, pt: Zcu.PerThread,
air_instructions: std.MultiArrayList(Air.Inst), air_instructions: std.MultiArrayList(Air.Inst),
air_extra: std.ArrayListUnmanaged(u32), air_extra: std.ArrayListUnmanaged(u32),
features: if (switch (dev.env) {
.bootstrap => @import("../codegen/c.zig").legalizeFeatures(undefined),
else => null,
}) |bootstrap_features| struct {
fn init(features: *const Features) @This() {
assert(features.eql(bootstrap_features.*));
return .{};
}
/// `inline` to propagate comptime-known result.
inline fn has(_: @This(), comptime feature: Feature) bool {
return comptime bootstrap_features.contains(feature);
}
/// `inline` to propagate comptime-known result.
fn hasAny(_: @This(), comptime features: []const Feature) bool {
return comptime !bootstrap_features.intersectWith(.initMany(features)).eql(.initEmpty());
}
} else struct {
features: *const Features, features: *const Features,
/// `inline` to propagate whether `dev.check` returns.
inline fn init(features: *const Features) @This() {
dev.check(.legalize);
return .{ .features = features };
}
fn has(rt: @This(), comptime feature: Feature) bool {
return rt.features.contains(feature);
}
fn hasAny(rt: @This(), comptime features: []const Feature) bool {
return !rt.features.intersectWith(comptime .initMany(features)).eql(comptime .initEmpty());
}
},
pub const Feature = enum { pub const Feature = enum {
scalarize_add, scalarize_add,
@ -83,6 +112,8 @@ pub const Feature = enum {
scalarize_trunc, scalarize_trunc,
scalarize_int_from_float, scalarize_int_from_float,
scalarize_int_from_float_optimized, scalarize_int_from_float_optimized,
scalarize_int_from_float_safe,
scalarize_int_from_float_optimized_safe,
scalarize_float_from_int, scalarize_float_from_int,
scalarize_shuffle_one, scalarize_shuffle_one,
scalarize_shuffle_two, scalarize_shuffle_two,
@ -97,6 +128,12 @@ pub const Feature = enum {
/// Replace `intcast_safe` with an explicit safety check which `call`s the panic function on failure. /// Replace `intcast_safe` with an explicit safety check which `call`s the panic function on failure.
/// Not compatible with `scalarize_intcast_safe`. /// Not compatible with `scalarize_intcast_safe`.
expand_intcast_safe, expand_intcast_safe,
/// Replace `int_from_float_safe` with an explicit safety check which `call`s the panic function on failure.
/// Not compatible with `scalarize_int_from_float_safe`.
expand_int_from_float_safe,
/// Replace `int_from_float_optimized_safe` with an explicit safety check which `call`s the panic function on failure.
/// Not compatible with `scalarize_int_from_float_optimized_safe`.
expand_int_from_float_optimized_safe,
/// Replace `add_safe` with an explicit safety check which `call`s the panic function on failure. /// Replace `add_safe` with an explicit safety check which `call`s the panic function on failure.
/// Not compatible with `scalarize_add_safe`. /// Not compatible with `scalarize_add_safe`.
expand_add_safe, expand_add_safe,
@ -196,10 +233,12 @@ pub const Feature = enum {
.trunc => .scalarize_trunc, .trunc => .scalarize_trunc,
.int_from_float => .scalarize_int_from_float, .int_from_float => .scalarize_int_from_float,
.int_from_float_optimized => .scalarize_int_from_float_optimized, .int_from_float_optimized => .scalarize_int_from_float_optimized,
.int_from_float_safe => .scalarize_int_from_float_safe,
.int_from_float_optimized_safe => .scalarize_int_from_float_optimized_safe,
.float_from_int => .scalarize_float_from_int, .float_from_int => .scalarize_float_from_int,
.shuffle_one => .scalarize_shuffle_one, .shuffle_one => .scalarize_shuffle_one,
.shuffle_two => .scalarize_shuffle_two, .shuffle_two => .scalarize_shuffle_two,
.select => .scalarize_selects, .select => .scalarize_select,
.mul_add => .scalarize_mul_add, .mul_add => .scalarize_mul_add,
}; };
} }
@ -210,13 +249,12 @@ pub const Features = std.enums.EnumSet(Feature);
pub const Error = std.mem.Allocator.Error; pub const Error = std.mem.Allocator.Error;
pub fn legalize(air: *Air, pt: Zcu.PerThread, features: *const Features) Error!void { pub fn legalize(air: *Air, pt: Zcu.PerThread, features: *const Features) Error!void {
dev.check(.legalize);
assert(!features.eql(comptime .initEmpty())); // backend asked to run legalize, but no features were enabled assert(!features.eql(comptime .initEmpty())); // backend asked to run legalize, but no features were enabled
var l: Legalize = .{ var l: Legalize = .{
.pt = pt, .pt = pt,
.air_instructions = air.instructions.toMultiArrayList(), .air_instructions = air.instructions.toMultiArrayList(),
.air_extra = air.extra, .air_extra = air.extra,
.features = features, .features = .init(features),
}; };
defer air.* = l.getTmpAir(); defer air.* = l.getTmpAir();
const main_extra = l.extraData(Air.Block, l.air_extra.items[@intFromEnum(Air.ExtraIndex.main_block)]); const main_extra = l.extraData(Air.Block, l.air_extra.items[@intFromEnum(Air.ExtraIndex.main_block)]);
@ -278,28 +316,28 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void {
.bit_and, .bit_and,
.bit_or, .bit_or,
.xor, .xor,
=> |air_tag| if (l.features.contains(comptime .scalarize(air_tag))) { => |air_tag| if (l.features.has(comptime .scalarize(air_tag))) {
const bin_op = l.air_instructions.items(.data)[@intFromEnum(inst)].bin_op; const bin_op = l.air_instructions.items(.data)[@intFromEnum(inst)].bin_op;
if (l.typeOf(bin_op.lhs).isVector(zcu)) continue :inst try l.scalarize(inst, .bin_op); if (l.typeOf(bin_op.lhs).isVector(zcu)) continue :inst try l.scalarize(inst, .bin_op);
}, },
.add_safe => if (l.features.contains(.expand_add_safe)) { .add_safe => if (l.features.has(.expand_add_safe)) {
assert(!l.features.contains(.scalarize_add_safe)); // it doesn't make sense to do both assert(!l.features.has(.scalarize_add_safe)); // it doesn't make sense to do both
continue :inst l.replaceInst(inst, .block, try l.safeArithmeticBlockPayload(inst, .add_with_overflow)); continue :inst l.replaceInst(inst, .block, try l.safeArithmeticBlockPayload(inst, .add_with_overflow));
} else if (l.features.contains(.scalarize_add_safe)) { } else if (l.features.has(.scalarize_add_safe)) {
const bin_op = l.air_instructions.items(.data)[@intFromEnum(inst)].bin_op; const bin_op = l.air_instructions.items(.data)[@intFromEnum(inst)].bin_op;
if (l.typeOf(bin_op.lhs).isVector(zcu)) continue :inst try l.scalarize(inst, .bin_op); if (l.typeOf(bin_op.lhs).isVector(zcu)) continue :inst try l.scalarize(inst, .bin_op);
}, },
.sub_safe => if (l.features.contains(.expand_sub_safe)) { .sub_safe => if (l.features.has(.expand_sub_safe)) {
assert(!l.features.contains(.scalarize_sub_safe)); // it doesn't make sense to do both assert(!l.features.has(.scalarize_sub_safe)); // it doesn't make sense to do both
continue :inst l.replaceInst(inst, .block, try l.safeArithmeticBlockPayload(inst, .sub_with_overflow)); continue :inst l.replaceInst(inst, .block, try l.safeArithmeticBlockPayload(inst, .sub_with_overflow));
} else if (l.features.contains(.scalarize_sub_safe)) { } else if (l.features.has(.scalarize_sub_safe)) {
const bin_op = l.air_instructions.items(.data)[@intFromEnum(inst)].bin_op; const bin_op = l.air_instructions.items(.data)[@intFromEnum(inst)].bin_op;
if (l.typeOf(bin_op.lhs).isVector(zcu)) continue :inst try l.scalarize(inst, .bin_op); if (l.typeOf(bin_op.lhs).isVector(zcu)) continue :inst try l.scalarize(inst, .bin_op);
}, },
.mul_safe => if (l.features.contains(.expand_mul_safe)) { .mul_safe => if (l.features.has(.expand_mul_safe)) {
assert(!l.features.contains(.scalarize_mul_safe)); // it doesn't make sense to do both assert(!l.features.has(.scalarize_mul_safe)); // it doesn't make sense to do both
continue :inst l.replaceInst(inst, .block, try l.safeArithmeticBlockPayload(inst, .mul_with_overflow)); continue :inst l.replaceInst(inst, .block, try l.safeArithmeticBlockPayload(inst, .mul_with_overflow));
} else if (l.features.contains(.scalarize_mul_safe)) { } else if (l.features.has(.scalarize_mul_safe)) {
const bin_op = l.air_instructions.items(.data)[@intFromEnum(inst)].bin_op; const bin_op = l.air_instructions.items(.data)[@intFromEnum(inst)].bin_op;
if (l.typeOf(bin_op.lhs).isVector(zcu)) continue :inst try l.scalarize(inst, .bin_op); if (l.typeOf(bin_op.lhs).isVector(zcu)) continue :inst try l.scalarize(inst, .bin_op);
}, },
@ -308,7 +346,7 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void {
.sub_with_overflow, .sub_with_overflow,
.mul_with_overflow, .mul_with_overflow,
.shl_with_overflow, .shl_with_overflow,
=> |air_tag| if (l.features.contains(comptime .scalarize(air_tag))) { => |air_tag| if (l.features.has(comptime .scalarize(air_tag))) {
const ty_pl = l.air_instructions.items(.data)[@intFromEnum(inst)].ty_pl; const ty_pl = l.air_instructions.items(.data)[@intFromEnum(inst)].ty_pl;
if (ty_pl.ty.toType().fieldType(0, zcu).isVector(zcu)) continue :inst l.replaceInst(inst, .block, try l.scalarizeOverflowBlockPayload(inst)); if (ty_pl.ty.toType().fieldType(0, zcu).isVector(zcu)) continue :inst l.replaceInst(inst, .block, try l.scalarizeOverflowBlockPayload(inst));
}, },
@ -320,13 +358,13 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void {
.shl, .shl,
.shl_exact, .shl_exact,
.shl_sat, .shl_sat,
=> |air_tag| if (!l.features.intersectWith(comptime .initMany(&.{ => |air_tag| if (l.features.hasAny(&.{
.unsplat_shift_rhs, .unsplat_shift_rhs,
.scalarize(air_tag), .scalarize(air_tag),
})).eql(comptime .initEmpty())) { })) {
const bin_op = l.air_instructions.items(.data)[@intFromEnum(inst)].bin_op; const bin_op = l.air_instructions.items(.data)[@intFromEnum(inst)].bin_op;
if (l.typeOf(bin_op.rhs).isVector(zcu)) { if (l.typeOf(bin_op.rhs).isVector(zcu)) {
if (l.features.contains(.unsplat_shift_rhs)) { if (l.features.has(.unsplat_shift_rhs)) {
if (bin_op.rhs.toInterned()) |rhs_ip_index| switch (ip.indexToKey(rhs_ip_index)) { if (bin_op.rhs.toInterned()) |rhs_ip_index| switch (ip.indexToKey(rhs_ip_index)) {
else => {}, else => {},
.aggregate => |aggregate| switch (aggregate.storage) { .aggregate => |aggregate| switch (aggregate.storage) {
@ -347,7 +385,7 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void {
} }
} }
} }
if (l.features.contains(comptime .scalarize(air_tag))) continue :inst try l.scalarize(inst, .bin_op); if (l.features.has(comptime .scalarize(air_tag))) continue :inst try l.scalarize(inst, .bin_op);
} }
}, },
inline .not, inline .not,
@ -364,11 +402,11 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void {
.int_from_float, .int_from_float,
.int_from_float_optimized, .int_from_float_optimized,
.float_from_int, .float_from_int,
=> |air_tag| if (l.features.contains(comptime .scalarize(air_tag))) { => |air_tag| if (l.features.has(comptime .scalarize(air_tag))) {
const ty_op = l.air_instructions.items(.data)[@intFromEnum(inst)].ty_op; const ty_op = l.air_instructions.items(.data)[@intFromEnum(inst)].ty_op;
if (ty_op.ty.toType().isVector(zcu)) continue :inst try l.scalarize(inst, .ty_op); if (ty_op.ty.toType().isVector(zcu)) continue :inst try l.scalarize(inst, .ty_op);
}, },
.bitcast => if (l.features.contains(.scalarize_bitcast)) { .bitcast => if (l.features.has(.scalarize_bitcast)) {
const ty_op = l.air_instructions.items(.data)[@intFromEnum(inst)].ty_op; const ty_op = l.air_instructions.items(.data)[@intFromEnum(inst)].ty_op;
const to_ty = ty_op.ty.toType(); const to_ty = ty_op.ty.toType();
@ -404,10 +442,24 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void {
}; };
if (!from_ty_legal) continue :inst l.replaceInst(inst, .block, try l.scalarizeBitcastOperandBlockPayload(inst)); if (!from_ty_legal) continue :inst l.replaceInst(inst, .block, try l.scalarizeBitcastOperandBlockPayload(inst));
}, },
.intcast_safe => if (l.features.contains(.expand_intcast_safe)) { .intcast_safe => if (l.features.has(.expand_intcast_safe)) {
assert(!l.features.contains(.scalarize_intcast_safe)); // it doesn't make sense to do both assert(!l.features.has(.scalarize_intcast_safe)); // it doesn't make sense to do both
continue :inst l.replaceInst(inst, .block, try l.safeIntcastBlockPayload(inst)); continue :inst l.replaceInst(inst, .block, try l.safeIntcastBlockPayload(inst));
} else if (l.features.contains(.scalarize_intcast_safe)) { } else if (l.features.has(.scalarize_intcast_safe)) {
const ty_op = l.air_instructions.items(.data)[@intFromEnum(inst)].ty_op;
if (ty_op.ty.toType().isVector(zcu)) continue :inst try l.scalarize(inst, .ty_op);
},
.int_from_float_safe => if (l.features.has(.expand_int_from_float_safe)) {
assert(!l.features.has(.scalarize_int_from_float_safe));
continue :inst l.replaceInst(inst, .block, try l.safeIntFromFloatBlockPayload(inst, false));
} else if (l.features.has(.scalarize_int_from_float_safe)) {
const ty_op = l.air_instructions.items(.data)[@intFromEnum(inst)].ty_op;
if (ty_op.ty.toType().isVector(zcu)) continue :inst try l.scalarize(inst, .ty_op);
},
.int_from_float_optimized_safe => if (l.features.has(.expand_int_from_float_optimized_safe)) {
assert(!l.features.has(.scalarize_int_from_float_optimized_safe));
continue :inst l.replaceInst(inst, .block, try l.safeIntFromFloatBlockPayload(inst, true));
} else if (l.features.has(.scalarize_int_from_float_optimized_safe)) {
const ty_op = l.air_instructions.items(.data)[@intFromEnum(inst)].ty_op; const ty_op = l.air_instructions.items(.data)[@intFromEnum(inst)].ty_op;
if (ty_op.ty.toType().isVector(zcu)) continue :inst try l.scalarize(inst, .ty_op); if (ty_op.ty.toType().isVector(zcu)) continue :inst try l.scalarize(inst, .ty_op);
}, },
@ -442,7 +494,7 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void {
.trunc_float, .trunc_float,
.neg, .neg,
.neg_optimized, .neg_optimized,
=> |air_tag| if (l.features.contains(comptime .scalarize(air_tag))) { => |air_tag| if (l.features.has(comptime .scalarize(air_tag))) {
const un_op = l.air_instructions.items(.data)[@intFromEnum(inst)].un_op; const un_op = l.air_instructions.items(.data)[@intFromEnum(inst)].un_op;
if (l.typeOf(un_op).isVector(zcu)) continue :inst try l.scalarize(inst, .un_op); if (l.typeOf(un_op).isVector(zcu)) continue :inst try l.scalarize(inst, .un_op);
}, },
@ -459,7 +511,7 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void {
.cmp_neq, .cmp_neq,
.cmp_neq_optimized, .cmp_neq_optimized,
=> {}, => {},
inline .cmp_vector, .cmp_vector_optimized => |air_tag| if (l.features.contains(comptime .scalarize(air_tag))) { inline .cmp_vector, .cmp_vector_optimized => |air_tag| if (l.features.has(comptime .scalarize(air_tag))) {
const ty_pl = l.air_instructions.items(.data)[@intFromEnum(inst)].ty_pl; const ty_pl = l.air_instructions.items(.data)[@intFromEnum(inst)].ty_pl;
if (ty_pl.ty.toType().isVector(zcu)) continue :inst try l.scalarize(inst, .cmp_vector); if (ty_pl.ty.toType().isVector(zcu)) continue :inst try l.scalarize(inst, .cmp_vector);
}, },
@ -513,13 +565,13 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void {
.bool_and, .bool_and,
.bool_or, .bool_or,
=> {}, => {},
.load => if (l.features.contains(.expand_packed_load)) { .load => if (l.features.has(.expand_packed_load)) {
const ty_op = l.air_instructions.items(.data)[@intFromEnum(inst)].ty_op; const ty_op = l.air_instructions.items(.data)[@intFromEnum(inst)].ty_op;
const ptr_info = l.typeOf(ty_op.operand).ptrInfo(zcu); const ptr_info = l.typeOf(ty_op.operand).ptrInfo(zcu);
if (ptr_info.packed_offset.host_size > 0 and ptr_info.flags.vector_index == .none) continue :inst l.replaceInst(inst, .block, try l.packedLoadBlockPayload(inst)); if (ptr_info.packed_offset.host_size > 0 and ptr_info.flags.vector_index == .none) continue :inst l.replaceInst(inst, .block, try l.packedLoadBlockPayload(inst));
}, },
.ret, .ret_safe, .ret_load => {}, .ret, .ret_safe, .ret_load => {},
.store, .store_safe => if (l.features.contains(.expand_packed_store)) { .store, .store_safe => if (l.features.has(.expand_packed_store)) {
const bin_op = l.air_instructions.items(.data)[@intFromEnum(inst)].bin_op; const bin_op = l.air_instructions.items(.data)[@intFromEnum(inst)].bin_op;
const ptr_info = l.typeOf(bin_op.lhs).ptrInfo(zcu); const ptr_info = l.typeOf(bin_op.lhs).ptrInfo(zcu);
if (ptr_info.packed_offset.host_size > 0 and ptr_info.flags.vector_index == .none) continue :inst l.replaceInst(inst, .block, try l.packedStoreBlockPayload(inst)); if (ptr_info.packed_offset.host_size > 0 and ptr_info.flags.vector_index == .none) continue :inst l.replaceInst(inst, .block, try l.packedStoreBlockPayload(inst));
@ -542,7 +594,7 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void {
.struct_field_ptr_index_2, .struct_field_ptr_index_2,
.struct_field_ptr_index_3, .struct_field_ptr_index_3,
=> {}, => {},
.struct_field_val => if (l.features.contains(.expand_packed_struct_field_val)) { .struct_field_val => if (l.features.has(.expand_packed_struct_field_val)) {
const ty_pl = l.air_instructions.items(.data)[@intFromEnum(inst)].ty_pl; const ty_pl = l.air_instructions.items(.data)[@intFromEnum(inst)].ty_pl;
const extra = l.extraData(Air.StructField, ty_pl.payload).data; const extra = l.extraData(Air.StructField, ty_pl.payload).data;
switch (l.typeOf(extra.struct_operand).containerLayout(zcu)) { switch (l.typeOf(extra.struct_operand).containerLayout(zcu)) {
@ -564,7 +616,7 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void {
.ptr_elem_ptr, .ptr_elem_ptr,
.array_to_slice, .array_to_slice,
=> {}, => {},
.reduce, .reduce_optimized => if (l.features.contains(.reduce_one_elem_to_bitcast)) { .reduce, .reduce_optimized => if (l.features.has(.reduce_one_elem_to_bitcast)) {
const reduce = l.air_instructions.items(.data)[@intFromEnum(inst)].reduce; const reduce = l.air_instructions.items(.data)[@intFromEnum(inst)].reduce;
const vector_ty = l.typeOf(reduce.operand); const vector_ty = l.typeOf(reduce.operand);
switch (vector_ty.vectorLen(zcu)) { switch (vector_ty.vectorLen(zcu)) {
@ -577,9 +629,9 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void {
} }
}, },
.splat => {}, .splat => {},
.shuffle_one => if (l.features.contains(.scalarize_shuffle_one)) continue :inst try l.scalarize(inst, .shuffle_one), .shuffle_one => if (l.features.has(.scalarize_shuffle_one)) continue :inst try l.scalarize(inst, .shuffle_one),
.shuffle_two => if (l.features.contains(.scalarize_shuffle_two)) continue :inst try l.scalarize(inst, .shuffle_two), .shuffle_two => if (l.features.has(.scalarize_shuffle_two)) continue :inst try l.scalarize(inst, .shuffle_two),
.select => if (l.features.contains(.scalarize_select)) continue :inst try l.scalarize(inst, .select), .select => if (l.features.has(.scalarize_select)) continue :inst try l.scalarize(inst, .select),
.memset, .memset,
.memset_safe, .memset_safe,
.memcpy, .memcpy,
@ -597,7 +649,7 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void {
.error_name, .error_name,
.error_set_has_value, .error_set_has_value,
=> {}, => {},
.aggregate_init => if (l.features.contains(.expand_packed_aggregate_init)) { .aggregate_init => if (l.features.has(.expand_packed_aggregate_init)) {
const ty_pl = l.air_instructions.items(.data)[@intFromEnum(inst)].ty_pl; const ty_pl = l.air_instructions.items(.data)[@intFromEnum(inst)].ty_pl;
const agg_ty = ty_pl.ty.toType(); const agg_ty = ty_pl.ty.toType();
switch (agg_ty.zigTypeTag(zcu)) { switch (agg_ty.zigTypeTag(zcu)) {
@ -609,7 +661,7 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void {
} }
}, },
.union_init, .prefetch => {}, .union_init, .prefetch => {},
.mul_add => if (l.features.contains(.scalarize_mul_add)) { .mul_add => if (l.features.has(.scalarize_mul_add)) {
const pl_op = l.air_instructions.items(.data)[@intFromEnum(inst)].pl_op; const pl_op = l.air_instructions.items(.data)[@intFromEnum(inst)].pl_op;
if (l.typeOf(pl_op.operand).isVector(zcu)) continue :inst try l.scalarize(inst, .pl_op_bin); if (l.typeOf(pl_op.operand).isVector(zcu)) continue :inst try l.scalarize(inst, .pl_op_bin);
}, },
@ -636,6 +688,7 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void {
} }
const ScalarizeForm = enum { un_op, ty_op, bin_op, pl_op_bin, bitcast, cmp_vector, shuffle_one, shuffle_two, select }; const ScalarizeForm = enum { un_op, ty_op, bin_op, pl_op_bin, bitcast, cmp_vector, shuffle_one, shuffle_two, select };
/// inline to propagate comptime-known `replaceInst` result.
inline fn scalarize(l: *Legalize, orig_inst: Air.Inst.Index, comptime form: ScalarizeForm) Error!Air.Inst.Tag { inline fn scalarize(l: *Legalize, orig_inst: Air.Inst.Index, comptime form: ScalarizeForm) Error!Air.Inst.Tag {
return l.replaceInst(orig_inst, .block, try l.scalarizeBlockPayload(orig_inst, form)); return l.replaceInst(orig_inst, .block, try l.scalarizeBlockPayload(orig_inst, form));
} }
@ -1972,6 +2025,115 @@ fn safeIntcastBlockPayload(l: *Legalize, orig_inst: Air.Inst.Index) Error!Air.In
.payload = try l.addBlockBody(main_block.body()), .payload = try l.addBlockBody(main_block.body()),
} }; } };
} }
fn safeIntFromFloatBlockPayload(l: *Legalize, orig_inst: Air.Inst.Index, optimized: bool) Error!Air.Inst.Data {
const pt = l.pt;
const zcu = pt.zcu;
const gpa = zcu.gpa;
const ty_op = l.air_instructions.items(.data)[@intFromEnum(orig_inst)].ty_op;
const operand_ref = ty_op.operand;
const operand_ty = l.typeOf(operand_ref);
const dest_ty = ty_op.ty.toType();
const is_vector = operand_ty.zigTypeTag(zcu) == .vector;
const dest_scalar_ty = dest_ty.scalarType(zcu);
const int_info = dest_scalar_ty.intInfo(zcu);
// We emit 9 instructions in the worst case.
var inst_buf: [9]Air.Inst.Index = undefined;
try l.air_instructions.ensureUnusedCapacity(zcu.gpa, inst_buf.len);
var main_block: Block = .init(&inst_buf);
// This check is a bit annoying because of floating-point rounding and the fact that this
// builtin truncates. We'll use a bigint for our calculations, because we need to construct
// integers exceeding the bounds of the result integer type, and we need to convert it to a
// float with a specific rounding mode to avoid errors.
// Our bigint may exceed the twos complement limit by one, so add an extra limb.
const limbs = try gpa.alloc(
std.math.big.Limb,
std.math.big.int.calcTwosCompLimbCount(int_info.bits) + 1,
);
defer gpa.free(limbs);
var big: std.math.big.int.Mutable = .init(limbs, 0);
// Check if the operand is lower than `min_int` when truncated to an integer.
big.setTwosCompIntLimit(.min, int_info.signedness, int_info.bits);
const below_min_inst: Air.Inst.Index = if (!big.positive or big.eqlZero()) bad: {
// `min_int <= 0`, so check for `x <= min_int - 1`.
big.addScalar(big.toConst(), -1);
// For `<=`, we must round the RHS down, so that this value is the first `x` which returns `true`.
const limit_val = try floatFromBigIntVal(pt, is_vector, operand_ty, big.toConst(), .floor);
break :bad try main_block.addCmp(l, .lte, operand_ref, Air.internedToRef(limit_val.toIntern()), .{
.vector = is_vector,
.optimized = optimized,
});
} else {
// `min_int > 0`, which is currently impossible. It would become possible under #3806, in
// which case we must detect `x < min_int`.
unreachable;
};
// Check if the operand is greater than `max_int` when truncated to an integer.
big.setTwosCompIntLimit(.max, int_info.signedness, int_info.bits);
const above_max_inst: Air.Inst.Index = if (big.positive or big.eqlZero()) bad: {
// `max_int >= 0`, so check for `x >= max_int + 1`.
big.addScalar(big.toConst(), 1);
// For `>=`, we must round the RHS up, so that this value is the first `x` which returns `true`.
const limit_val = try floatFromBigIntVal(pt, is_vector, operand_ty, big.toConst(), .ceil);
break :bad try main_block.addCmp(l, .gte, operand_ref, Air.internedToRef(limit_val.toIntern()), .{
.vector = is_vector,
.optimized = optimized,
});
} else {
// `max_int < 0`, which is currently impossible. It would become possible under #3806, in
// which case we must detect `x > max_int`.
unreachable;
};
// Combine the conditions.
const out_of_bounds_inst: Air.Inst.Index = main_block.add(l, .{
.tag = .bool_or,
.data = .{ .bin_op = .{
.lhs = below_min_inst.toRef(),
.rhs = above_max_inst.toRef(),
} },
});
const scalar_out_of_bounds_inst: Air.Inst.Index = if (is_vector) main_block.add(l, .{
.tag = .reduce,
.data = .{ .reduce = .{
.operand = out_of_bounds_inst.toRef(),
.operation = .Or,
} },
}) else out_of_bounds_inst;
// Now emit the actual condbr. "true" will be safety panic. "false" will be "ok", meaning we do
// the `int_from_float` and `br` the result to `orig_inst`.
var condbr: CondBr = .init(l, scalar_out_of_bounds_inst.toRef(), &main_block, .{ .true = .cold });
condbr.then_block = .init(main_block.stealRemainingCapacity());
try condbr.then_block.addPanic(l, .integer_part_out_of_bounds);
condbr.else_block = .init(condbr.then_block.stealRemainingCapacity());
const cast_inst = condbr.else_block.add(l, .{
.tag = if (optimized) .int_from_float_optimized else .int_from_float,
.data = .{ .ty_op = .{
.ty = Air.internedToRef(dest_ty.toIntern()),
.operand = operand_ref,
} },
});
_ = condbr.else_block.add(l, .{
.tag = .br,
.data = .{ .br = .{
.block_inst = orig_inst,
.operand = cast_inst.toRef(),
} },
});
_ = condbr.else_block.stealRemainingCapacity(); // we might not have used it all
try condbr.finish(l);
return .{ .ty_pl = .{
.ty = Air.internedToRef(dest_ty.toIntern()),
.payload = try l.addBlockBody(main_block.body()),
} };
}
fn safeArithmeticBlockPayload(l: *Legalize, orig_inst: Air.Inst.Index, overflow_op_tag: Air.Inst.Tag) Error!Air.Inst.Data { fn safeArithmeticBlockPayload(l: *Legalize, orig_inst: Air.Inst.Index, overflow_op_tag: Air.Inst.Tag) Error!Air.Inst.Data {
const pt = l.pt; const pt = l.pt;
const zcu = pt.zcu; const zcu = pt.zcu;
@ -2349,6 +2511,42 @@ fn packedAggregateInitBlockPayload(l: *Legalize, orig_inst: Air.Inst.Index) Erro
} }; } };
} }
/// Given a `std.math.big.int.Const`, converts it to a `Value` which is a float of type `float_ty`
/// representing the same numeric value. If the integer cannot be exactly represented, `round`
/// decides whether the value should be rounded up or down. If `is_vector`, then `float_ty` is
/// instead a vector of floats, and the result value is a vector containing the converted scalar
/// repeated N times.
fn floatFromBigIntVal(
pt: Zcu.PerThread,
is_vector: bool,
float_ty: Type,
x: std.math.big.int.Const,
round: std.math.big.int.Round,
) Error!Value {
const zcu = pt.zcu;
const scalar_ty = switch (is_vector) {
true => float_ty.childType(zcu),
false => float_ty,
};
assert(scalar_ty.zigTypeTag(zcu) == .float);
const scalar_val: Value = switch (scalar_ty.floatBits(zcu.getTarget())) {
16 => try pt.floatValue(scalar_ty, x.toFloat(f16, round)[0]),
32 => try pt.floatValue(scalar_ty, x.toFloat(f32, round)[0]),
64 => try pt.floatValue(scalar_ty, x.toFloat(f64, round)[0]),
80 => try pt.floatValue(scalar_ty, x.toFloat(f80, round)[0]),
128 => try pt.floatValue(scalar_ty, x.toFloat(f128, round)[0]),
else => unreachable,
};
if (is_vector) {
return .fromInterned(try pt.intern(.{ .aggregate = .{
.ty = float_ty.toIntern(),
.storage = .{ .repeated_elem = scalar_val.toIntern() },
} }));
} else {
return scalar_val;
}
}
const Block = struct { const Block = struct {
instructions: []Air.Inst.Index, instructions: []Air.Inst.Index,
len: usize, len: usize,
@ -2691,7 +2889,7 @@ fn addBlockBody(l: *Legalize, body: []const Air.Inst.Index) Error!u32 {
} }
/// Returns `tag` to remind the caller to `continue :inst` the result. /// Returns `tag` to remind the caller to `continue :inst` the result.
/// This is inline to propagate the comptime-known `tag`. /// `inline` to propagate the comptime-known `tag` result.
inline fn replaceInst(l: *Legalize, inst: Air.Inst.Index, comptime tag: Air.Inst.Tag, data: Air.Inst.Data) Air.Inst.Tag { inline fn replaceInst(l: *Legalize, inst: Air.Inst.Index, comptime tag: Air.Inst.Tag, data: Air.Inst.Data) Air.Inst.Tag {
const orig_ty = if (std.debug.runtime_safety) l.typeOfIndex(inst) else {}; const orig_ty = if (std.debug.runtime_safety) l.typeOfIndex(inst) else {};
l.air_instructions.set(@intFromEnum(inst), .{ .tag = tag, .data = data }); l.air_instructions.set(@intFromEnum(inst), .{ .tag = tag, .data = data });
@ -2706,4 +2904,5 @@ const InternPool = @import("../InternPool.zig");
const Legalize = @This(); const Legalize = @This();
const std = @import("std"); const std = @import("std");
const Type = @import("../Type.zig"); const Type = @import("../Type.zig");
const Value = @import("../Value.zig");
const Zcu = @import("../Zcu.zig"); const Zcu = @import("../Zcu.zig");

View File

@ -374,6 +374,8 @@ pub fn categorizeOperand(
.array_to_slice, .array_to_slice,
.int_from_float, .int_from_float,
.int_from_float_optimized, .int_from_float_optimized,
.int_from_float_safe,
.int_from_float_optimized_safe,
.float_from_int, .float_from_int,
.get_union_tag, .get_union_tag,
.clz, .clz,
@ -1015,6 +1017,8 @@ fn analyzeInst(
.array_to_slice, .array_to_slice,
.int_from_float, .int_from_float,
.int_from_float_optimized, .int_from_float_optimized,
.int_from_float_safe,
.int_from_float_optimized_safe,
.float_from_int, .float_from_int,
.get_union_tag, .get_union_tag,
.clz, .clz,

View File

@ -107,6 +107,8 @@ fn verifyBody(self: *Verify, body: []const Air.Inst.Index) Error!void {
.array_to_slice, .array_to_slice,
.int_from_float, .int_from_float,
.int_from_float_optimized, .int_from_float_optimized,
.int_from_float_safe,
.int_from_float_optimized_safe,
.float_from_int, .float_from_int,
.get_union_tag, .get_union_tag,
.clz, .clz,

View File

@ -250,6 +250,8 @@ const Writer = struct {
.splat, .splat,
.int_from_float, .int_from_float,
.int_from_float_optimized, .int_from_float_optimized,
.int_from_float_safe,
.int_from_float_optimized_safe,
.get_union_tag, .get_union_tag,
.clz, .clz,
.ctz, .ctz,

View File

@ -130,6 +130,8 @@ fn checkBody(air: Air, body: []const Air.Inst.Index, zcu: *Zcu) bool {
.array_to_slice, .array_to_slice,
.int_from_float, .int_from_float,
.int_from_float_optimized, .int_from_float_optimized,
.int_from_float_safe,
.int_from_float_optimized_safe,
.float_from_int, .float_from_int,
.splat, .splat,
.error_set_has_value, .error_set_has_value,

View File

@ -8934,9 +8934,7 @@ fn zirEnumFromInt(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
try sema.requireRuntimeBlock(block, src, operand_src); try sema.requireRuntimeBlock(block, src, operand_src);
if (block.wantSafety()) { if (block.wantSafety()) {
if (zcu.backendSupportsFeature(.panic_fn)) { try sema.preparePanicId(src, .invalid_enum_value);
_ = try sema.preparePanicId(src, .invalid_enum_value);
}
return block.addTyOp(.intcast_safe, dest_ty, operand); return block.addTyOp(.intcast_safe, dest_ty, operand);
} }
return block.addTyOp(.intcast, dest_ty, operand); return block.addTyOp(.intcast, dest_ty, operand);
@ -10340,9 +10338,7 @@ fn intCast(
try sema.requireRuntimeBlock(block, src, operand_src); try sema.requireRuntimeBlock(block, src, operand_src);
if (block.wantSafety()) { if (block.wantSafety()) {
if (zcu.backendSupportsFeature(.panic_fn)) { try sema.preparePanicId(src, .integer_out_of_bounds);
_ = try sema.preparePanicId(src, .integer_out_of_bounds);
}
return block.addTyOp(.intcast_safe, dest_ty, operand); return block.addTyOp(.intcast_safe, dest_ty, operand);
} }
return block.addTyOp(.intcast, dest_ty, operand); return block.addTyOp(.intcast, dest_ty, operand);
@ -16395,9 +16391,7 @@ fn analyzeArithmetic(
} }
if (block.wantSafety() and want_safety and scalar_tag == .int) { if (block.wantSafety() and want_safety and scalar_tag == .int) {
if (air_tag != air_tag_safe and zcu.backendSupportsFeature(.panic_fn)) { if (air_tag != air_tag_safe) try sema.preparePanicId(src, .integer_overflow);
_ = try sema.preparePanicId(src, .integer_overflow);
}
return block.addBinOp(air_tag_safe, casted_lhs, casted_rhs); return block.addBinOp(air_tag_safe, casted_lhs, casted_rhs);
} }
return block.addBinOp(air_tag, casted_lhs, casted_rhs); return block.addBinOp(air_tag, casted_lhs, casted_rhs);
@ -22178,44 +22172,32 @@ fn zirIntFromFloat(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileErro
try sema.requireRuntimeBlock(block, src, operand_src); try sema.requireRuntimeBlock(block, src, operand_src);
if (dest_scalar_ty.intInfo(zcu).bits == 0) { if (dest_scalar_ty.intInfo(zcu).bits == 0) {
if (!is_vector) {
if (block.wantSafety()) { if (block.wantSafety()) {
const ok = try block.addBinOp(if (block.float_mode == .optimized) .cmp_eq_optimized else .cmp_eq, operand, Air.internedToRef((try pt.floatValue(operand_ty, 0.0)).toIntern())); // Emit an explicit safety check. We can do this one like `abs(x) < 1`.
try sema.addSafetyCheck(block, src, ok, .integer_part_out_of_bounds); const abs_ref = try block.addTyOp(.abs, operand_ty, operand);
} const max_abs_ref = if (is_vector) try block.addReduce(abs_ref, .Max) else abs_ref;
return Air.internedToRef((try pt.intValue(dest_ty, 0)).toIntern()); const one_ref = Air.internedToRef((try pt.floatValue(operand_scalar_ty, 1.0)).toIntern());
} const ok_ref = try block.addBinOp(.cmp_lt, max_abs_ref, one_ref);
if (block.wantSafety()) { try sema.addSafetyCheck(block, src, ok_ref, .integer_part_out_of_bounds);
const len = dest_ty.vectorLen(zcu);
for (0..len) |i| {
const idx_ref = try pt.intRef(.usize, i);
const elem_ref = try block.addBinOp(.array_elem_val, operand, idx_ref);
const ok = try block.addBinOp(if (block.float_mode == .optimized) .cmp_eq_optimized else .cmp_eq, elem_ref, Air.internedToRef((try pt.floatValue(operand_scalar_ty, 0.0)).toIntern()));
try sema.addSafetyCheck(block, src, ok, .integer_part_out_of_bounds);
}
} }
const scalar_val = try pt.intValue(dest_scalar_ty, 0);
if (!is_vector) return Air.internedToRef(scalar_val.toIntern());
return Air.internedToRef(try pt.intern(.{ .aggregate = .{ return Air.internedToRef(try pt.intern(.{ .aggregate = .{
.ty = dest_ty.toIntern(), .ty = dest_ty.toIntern(),
.storage = .{ .repeated_elem = (try pt.intValue(dest_scalar_ty, 0)).toIntern() }, .storage = .{ .repeated_elem = scalar_val.toIntern() },
} })); } }));
} }
const result = try block.addTyOp(if (block.float_mode == .optimized) .int_from_float_optimized else .int_from_float, dest_ty, operand);
if (block.wantSafety()) { if (block.wantSafety()) {
const back = try block.addTyOp(.float_from_int, operand_ty, result); try sema.preparePanicId(src, .integer_part_out_of_bounds);
const diff = try block.addBinOp(if (block.float_mode == .optimized) .sub_optimized else .sub, operand, back); return block.addTyOp(switch (block.float_mode) {
const ok = if (is_vector) ok: { .optimized => .int_from_float_optimized_safe,
const ok_pos = try block.addCmpVector(diff, Air.internedToRef((try sema.splat(operand_ty, try pt.floatValue(operand_scalar_ty, 1.0))).toIntern()), .lt); .strict => .int_from_float_safe,
const ok_neg = try block.addCmpVector(diff, Air.internedToRef((try sema.splat(operand_ty, try pt.floatValue(operand_scalar_ty, -1.0))).toIntern()), .gt); }, dest_ty, operand);
const ok = try block.addBinOp(.bit_and, ok_pos, ok_neg);
break :ok try block.addReduce(ok, .And);
} else ok: {
const ok_pos = try block.addBinOp(if (block.float_mode == .optimized) .cmp_lt_optimized else .cmp_lt, diff, Air.internedToRef((try pt.floatValue(operand_ty, 1.0)).toIntern()));
const ok_neg = try block.addBinOp(if (block.float_mode == .optimized) .cmp_gt_optimized else .cmp_gt, diff, Air.internedToRef((try pt.floatValue(operand_ty, -1.0)).toIntern()));
break :ok try block.addBinOp(.bool_and, ok_pos, ok_neg);
};
try sema.addSafetyCheck(block, src, ok, .integer_part_out_of_bounds);
} }
return result; return block.addTyOp(switch (block.float_mode) {
.optimized => .int_from_float_optimized,
.strict => .int_from_float,
}, dest_ty, operand);
} }
fn zirFloatFromInt(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { fn zirFloatFromInt(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
@ -26871,7 +26853,15 @@ fn explainWhyTypeIsNotPacked(
/// Backends depend on panic decls being available when lowering safety-checked /// Backends depend on panic decls being available when lowering safety-checked
/// instructions. This function ensures the panic function will be available to /// instructions. This function ensures the panic function will be available to
/// be called during that time. /// be called during that time.
fn preparePanicId(sema: *Sema, src: LazySrcLoc, panic_id: Zcu.SimplePanicId) !InternPool.Index { fn preparePanicId(sema: *Sema, src: LazySrcLoc, panic_id: Zcu.SimplePanicId) !void {
// If the backend doesn't support `.panic_fn`, it doesn't want us to lower the panic handlers.
// The backend will transform panics into traps instead.
if (sema.pt.zcu.backendSupportsFeature(.panic_fn)) {
_ = try sema.getPanicIdFunc(src, panic_id);
}
}
fn getPanicIdFunc(sema: *Sema, src: LazySrcLoc, panic_id: Zcu.SimplePanicId) !InternPool.Index {
const zcu = sema.pt.zcu; const zcu = sema.pt.zcu;
try sema.ensureMemoizedStateResolved(src, .panic); try sema.ensureMemoizedStateResolved(src, .panic);
const panic_func = zcu.builtin_decl_values.get(panic_id.toBuiltin()); const panic_func = zcu.builtin_decl_values.get(panic_id.toBuiltin());
@ -27120,7 +27110,7 @@ fn safetyPanic(sema: *Sema, block: *Block, src: LazySrcLoc, panic_id: Zcu.Simple
if (!sema.pt.zcu.backendSupportsFeature(.panic_fn)) { if (!sema.pt.zcu.backendSupportsFeature(.panic_fn)) {
_ = try block.addNoOp(.trap); _ = try block.addNoOp(.trap);
} else { } else {
const panic_fn = try sema.preparePanicId(src, panic_id); const panic_fn = try sema.getPanicIdFunc(src, panic_id);
try sema.callBuiltin(block, src, Air.internedToRef(panic_fn), .auto, &.{}, .@"safety check"); try sema.callBuiltin(block, src, Air.internedToRef(panic_fn), .auto, &.{}, .@"safety check");
} }
} }
@ -32843,24 +32833,21 @@ fn cmpNumeric(
} }
} }
if (lhs_is_float) { if (lhs_is_float) {
if (lhs_val.floatHasFraction(zcu)) { const float = lhs_val.toFloat(f128, zcu);
switch (op) { var big_int: std.math.big.int.Mutable = .{
.limbs = try sema.arena.alloc(std.math.big.Limb, std.math.big.int.calcLimbLen(float)),
.len = undefined,
.positive = undefined,
};
switch (big_int.setFloat(float, .away)) {
.inexact => switch (op) {
.eq => return .bool_false, .eq => return .bool_false,
.neq => return .bool_true, .neq => return .bool_true,
else => {}, else => {},
},
.exact => {},
} }
} lhs_bits = big_int.toConst().bitCountTwosComp();
var bigint = try float128IntPartToBigInt(sema.gpa, lhs_val.toFloat(f128, zcu));
defer bigint.deinit();
if (lhs_val.floatHasFraction(zcu)) {
if (lhs_is_signed) {
try bigint.addScalar(&bigint, -1);
} else {
try bigint.addScalar(&bigint, 1);
}
}
lhs_bits = bigint.toConst().bitCountTwosComp();
} else { } else {
lhs_bits = lhs_val.intBitCountTwosComp(zcu); lhs_bits = lhs_val.intBitCountTwosComp(zcu);
} }
@ -32890,24 +32877,21 @@ fn cmpNumeric(
} }
} }
if (rhs_is_float) { if (rhs_is_float) {
if (rhs_val.floatHasFraction(zcu)) { const float = rhs_val.toFloat(f128, zcu);
switch (op) { var big_int: std.math.big.int.Mutable = .{
.limbs = try sema.arena.alloc(std.math.big.Limb, std.math.big.int.calcLimbLen(float)),
.len = undefined,
.positive = undefined,
};
switch (big_int.setFloat(float, .away)) {
.inexact => switch (op) {
.eq => return .bool_false, .eq => return .bool_false,
.neq => return .bool_true, .neq => return .bool_true,
else => {}, else => {},
},
.exact => {},
} }
} rhs_bits = big_int.toConst().bitCountTwosComp();
var bigint = try float128IntPartToBigInt(sema.gpa, rhs_val.toFloat(f128, zcu));
defer bigint.deinit();
if (rhs_val.floatHasFraction(zcu)) {
if (rhs_is_signed) {
try bigint.addScalar(&bigint, -1);
} else {
try bigint.addScalar(&bigint, 1);
}
}
rhs_bits = bigint.toConst().bitCountTwosComp();
} else { } else {
rhs_bits = rhs_val.intBitCountTwosComp(zcu); rhs_bits = rhs_val.intBitCountTwosComp(zcu);
} }
@ -36955,31 +36939,6 @@ fn intFromFloat(
return sema.intFromFloatScalar(block, src, val, int_ty, mode); return sema.intFromFloatScalar(block, src, val, int_ty, mode);
} }
// float is expected to be finite and non-NaN
fn float128IntPartToBigInt(
arena: Allocator,
float: f128,
) !std.math.big.int.Managed {
const is_negative = std.math.signbit(float);
const floored = @floor(@abs(float));
var rational = try std.math.big.Rational.init(arena);
defer rational.q.deinit();
rational.setFloat(f128, floored) catch |err| switch (err) {
error.NonFiniteFloat => unreachable,
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();
}
return rational.p;
}
fn intFromFloatScalar( fn intFromFloatScalar(
sema: *Sema, sema: *Sema,
block: *Block, block: *Block,
@ -36993,13 +36952,6 @@ fn intFromFloatScalar(
if (val.isUndef(zcu)) return sema.failWithUseOfUndef(block, src); if (val.isUndef(zcu)) return sema.failWithUseOfUndef(block, src);
if (mode == .exact and val.floatHasFraction(zcu)) return sema.fail(
block,
src,
"fractional component prevents float value '{}' from coercion to type '{}'",
.{ val.fmtValueSema(pt, sema), int_ty.fmt(pt) },
);
const float = val.toFloat(f128, zcu); const float = val.toFloat(f128, zcu);
if (std.math.isNan(float)) { if (std.math.isNan(float)) {
return sema.fail(block, src, "float value NaN cannot be stored in integer type '{}'", .{ return sema.fail(block, src, "float value NaN cannot be stored in integer type '{}'", .{
@ -37012,12 +36964,28 @@ fn intFromFloatScalar(
}); });
} }
var big_int = try float128IntPartToBigInt(sema.arena, float); var big_int: std.math.big.int.Mutable = .{
defer big_int.deinit(); .limbs = try sema.arena.alloc(std.math.big.Limb, std.math.big.int.calcLimbLen(float)),
.len = undefined,
.positive = undefined,
};
switch (big_int.setFloat(float, .trunc)) {
.inexact => switch (mode) {
.exact => return sema.fail(
block,
src,
"fractional component prevents float value '{}' from coercion to type '{}'",
.{ val.fmtValueSema(pt, sema), int_ty.fmt(pt) },
),
.truncate => {},
},
.exact => {},
}
const cti_result = try pt.intValue_big(.comptime_int, big_int.toConst()); const cti_result = try pt.intValue_big(.comptime_int, big_int.toConst());
if (int_ty.toIntern() == .comptime_int_type) return cti_result;
if (!(try sema.intFitsInType(cti_result, int_ty, null))) { const int_info = int_ty.intInfo(zcu);
if (!big_int.toConst().fitsInTwosComp(int_info.signedness, int_info.bits)) {
return sema.fail(block, src, "float value '{}' cannot be stored in integer type '{}'", .{ return sema.fail(block, src, "float value '{}' cannot be stored in integer type '{}'", .{
val.fmtValueSema(pt, sema), int_ty.fmt(pt), val.fmtValueSema(pt, sema), int_ty.fmt(pt),
}); });

View File

@ -509,30 +509,23 @@ fn lowerInt(
}, },
}, },
.float_literal => |val| { .float_literal => |val| {
// Check for fractional components var big_int: std.math.big.int.Mutable = .{
if (@rem(val, 1) != 0) { .limbs = try self.sema.arena.alloc(std.math.big.Limb, std.math.big.int.calcLimbLen(val)),
return self.fail( .len = undefined,
.positive = undefined,
};
switch (big_int.setFloat(val, .trunc)) {
.inexact => return self.fail(
node, node,
"fractional component prevents float value '{}' from coercion to type '{}'", "fractional component prevents float value '{}' from coercion to type '{}'",
.{ val, res_ty.fmt(self.sema.pt) }, .{ val, res_ty.fmt(self.sema.pt) },
); ),
.exact => {},
} }
// Create a rational representation of the float
var rational = try std.math.big.Rational.init(self.sema.arena);
rational.setFloat(f128, val) catch |err| switch (err) {
error.NonFiniteFloat => unreachable,
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));
// Check that the result is in range of the result type // Check that the result is in range of the result type
const int_info = res_ty.intInfo(self.sema.pt.zcu); const int_info = res_ty.intInfo(self.sema.pt.zcu);
if (!rational.p.fitsInTwosComp(int_info.signedness, int_info.bits)) { if (!big_int.toConst().fitsInTwosComp(int_info.signedness, int_info.bits)) {
return self.fail( return self.fail(
node, node,
"type '{}' cannot represent integer value '{}'", "type '{}' cannot represent integer value '{}'",
@ -543,7 +536,7 @@ fn lowerInt(
return self.sema.pt.intern(.{ return self.sema.pt.intern(.{
.int = .{ .int = .{
.ty = res_ty.toIntern(), .ty = res_ty.toIntern(),
.storage = .{ .big_int = rational.p.toConst() }, .storage = .{ .big_int = big_int.toConst() },
}, },
}); });
}, },
@ -584,7 +577,7 @@ fn lowerFloat(
const value = switch (node.get(self.file.zoir.?)) { const value = switch (node.get(self.file.zoir.?)) {
.int_literal => |int| switch (int) { .int_literal => |int| switch (int) {
.small => |val| try self.sema.pt.floatValue(res_ty, @as(f128, @floatFromInt(val))), .small => |val| try self.sema.pt.floatValue(res_ty, @as(f128, @floatFromInt(val))),
.big => |val| try self.sema.pt.floatValue(res_ty, val.toFloat(f128)), .big => |val| try self.sema.pt.floatValue(res_ty, val.toFloat(f128, .nearest_even)[0]),
}, },
.float_literal => |val| try self.sema.pt.floatValue(res_ty, val), .float_literal => |val| try self.sema.pt.floatValue(res_ty, val),
.char_literal => |val| try self.sema.pt.floatValue(res_ty, @as(f128, @floatFromInt(val))), .char_literal => |val| try self.sema.pt.floatValue(res_ty, @as(f128, @floatFromInt(val))),

View File

@ -898,7 +898,7 @@ pub fn readFromPackedMemory(
pub fn toFloat(val: Value, comptime T: type, zcu: *const Zcu) T { pub fn toFloat(val: Value, comptime T: type, zcu: *const Zcu) T {
return switch (zcu.intern_pool.indexToKey(val.toIntern())) { return switch (zcu.intern_pool.indexToKey(val.toIntern())) {
.int => |int| switch (int.storage) { .int => |int| switch (int.storage) {
.big_int => |big_int| big_int.toFloat(T), .big_int => |big_int| big_int.toFloat(T, .nearest_even)[0],
inline .u64, .i64 => |x| { inline .u64, .i64 => |x| {
if (T == f80) { if (T == f80) {
@panic("TODO we can't lower this properly on non-x86 llvm backend yet"); @panic("TODO we can't lower this properly on non-x86 llvm backend yet");
@ -997,16 +997,6 @@ pub fn floatCast(val: Value, dest_ty: Type, pt: Zcu.PerThread) !Value {
} })); } }));
} }
/// Asserts the value is a float
pub fn floatHasFraction(self: Value, zcu: *const Zcu) bool {
return switch (zcu.intern_pool.indexToKey(self.toIntern())) {
.float => |float| switch (float.storage) {
inline else => |x| @rem(x, 1) != 0,
},
else => unreachable,
};
}
pub fn orderAgainstZero(lhs: Value, zcu: *Zcu) std.math.Order { pub fn orderAgainstZero(lhs: Value, zcu: *Zcu) std.math.Order {
return orderAgainstZeroInner(lhs, .normal, zcu, {}) catch unreachable; return orderAgainstZeroInner(lhs, .normal, zcu, {}) catch unreachable;
} }
@ -1557,17 +1547,13 @@ pub fn floatFromIntAdvanced(
} }
pub fn floatFromIntScalar(val: Value, float_ty: Type, pt: Zcu.PerThread, comptime strat: ResolveStrat) !Value { pub fn floatFromIntScalar(val: Value, float_ty: Type, pt: Zcu.PerThread, comptime strat: ResolveStrat) !Value {
const zcu = pt.zcu; return switch (pt.zcu.intern_pool.indexToKey(val.toIntern())) {
return switch (zcu.intern_pool.indexToKey(val.toIntern())) {
.undef => try pt.undefValue(float_ty), .undef => try pt.undefValue(float_ty),
.int => |int| switch (int.storage) { .int => |int| switch (int.storage) {
.big_int => |big_int| { .big_int => |big_int| pt.floatValue(float_ty, big_int.toFloat(f128, .nearest_even)[0]),
const float = big_int.toFloat(f128);
return pt.floatValue(float_ty, float);
},
inline .u64, .i64 => |x| floatFromIntInner(x, float_ty, pt), inline .u64, .i64 => |x| floatFromIntInner(x, float_ty, pt),
.lazy_align => |ty| return floatFromIntInner((try Type.fromInterned(ty).abiAlignmentInner(strat.toLazy(), pt.zcu, pt.tid)).scalar.toByteUnits() orelse 0, float_ty, pt), .lazy_align => |ty| floatFromIntInner((try Type.fromInterned(ty).abiAlignmentInner(strat.toLazy(), pt.zcu, pt.tid)).scalar.toByteUnits() orelse 0, float_ty, pt),
.lazy_size => |ty| return floatFromIntInner((try Type.fromInterned(ty).abiSizeInner(strat.toLazy(), pt.zcu, pt.tid)).scalar, float_ty, pt), .lazy_size => |ty| floatFromIntInner((try Type.fromInterned(ty).abiSizeInner(strat.toLazy(), pt.zcu, pt.tid)).scalar, float_ty, pt),
}, },
else => unreachable, else => unreachable,
}; };

View File

@ -861,6 +861,8 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
.sub_safe, .sub_safe,
.mul_safe, .mul_safe,
.intcast_safe, .intcast_safe,
.int_from_float_safe,
.int_from_float_optimized_safe,
=> return self.fail("TODO implement safety_checked_instructions", .{}), => return self.fail("TODO implement safety_checked_instructions", .{}),
.is_named_enum_value => return self.fail("TODO implement is_named_enum_value", .{}), .is_named_enum_value => return self.fail("TODO implement is_named_enum_value", .{}),

View File

@ -850,6 +850,8 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
.sub_safe, .sub_safe,
.mul_safe, .mul_safe,
.intcast_safe, .intcast_safe,
.int_from_float_safe,
.int_from_float_optimized_safe,
=> return self.fail("TODO implement safety_checked_instructions", .{}), => return self.fail("TODO implement safety_checked_instructions", .{}),
.is_named_enum_value => return self.fail("TODO implement is_named_enum_value", .{}), .is_named_enum_value => return self.fail("TODO implement is_named_enum_value", .{}),

View File

@ -54,6 +54,8 @@ const InnerError = CodeGenError || error{OutOfRegisters};
pub fn legalizeFeatures(_: *const std.Target) *const Air.Legalize.Features { pub fn legalizeFeatures(_: *const std.Target) *const Air.Legalize.Features {
return comptime &.initMany(&.{ return comptime &.initMany(&.{
.expand_intcast_safe, .expand_intcast_safe,
.expand_int_from_float_safe,
.expand_int_from_float_optimized_safe,
.expand_add_safe, .expand_add_safe,
.expand_sub_safe, .expand_sub_safe,
.expand_mul_safe, .expand_mul_safe,
@ -1474,6 +1476,8 @@ fn genBody(func: *Func, body: []const Air.Inst.Index) InnerError!void {
.sub_safe, .sub_safe,
.mul_safe, .mul_safe,
.intcast_safe, .intcast_safe,
.int_from_float_safe,
.int_from_float_optimized_safe,
=> return func.fail("TODO implement safety_checked_instructions", .{}), => return func.fail("TODO implement safety_checked_instructions", .{}),
.cmp_lt, .cmp_lt,

View File

@ -696,6 +696,8 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
.sub_safe, .sub_safe,
.mul_safe, .mul_safe,
.intcast_safe, .intcast_safe,
.int_from_float_safe,
.int_from_float_optimized_safe,
=> @panic("TODO implement safety_checked_instructions"), => @panic("TODO implement safety_checked_instructions"),
.is_named_enum_value => @panic("TODO implement is_named_enum_value"), .is_named_enum_value => @panic("TODO implement is_named_enum_value"),

View File

@ -31,6 +31,8 @@ const compilerRtIntAbbrev = target_util.compilerRtIntAbbrev;
pub fn legalizeFeatures(_: *const std.Target) *const Air.Legalize.Features { pub fn legalizeFeatures(_: *const std.Target) *const Air.Legalize.Features {
return comptime &.initMany(&.{ return comptime &.initMany(&.{
.expand_intcast_safe, .expand_intcast_safe,
.expand_int_from_float_safe,
.expand_int_from_float_optimized_safe,
.expand_add_safe, .expand_add_safe,
.expand_sub_safe, .expand_sub_safe,
.expand_mul_safe, .expand_mul_safe,
@ -2020,6 +2022,8 @@ fn genInst(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void {
.sub_safe, .sub_safe,
.mul_safe, .mul_safe,
.intcast_safe, .intcast_safe,
.int_from_float_safe,
.int_from_float_optimized_safe,
=> return cg.fail("TODO implement safety_checked_instructions", .{}), => return cg.fail("TODO implement safety_checked_instructions", .{}),
.work_item_id, .work_item_id,

View File

@ -102,6 +102,8 @@ pub fn legalizeFeatures(target: *const std.Target) *const Air.Legalize.Features
.reduce_one_elem_to_bitcast = true, .reduce_one_elem_to_bitcast = true,
.expand_intcast_safe = true, .expand_intcast_safe = true,
.expand_int_from_float_safe = true,
.expand_int_from_float_optimized_safe = true,
.expand_add_safe = true, .expand_add_safe = true,
.expand_sub_safe = true, .expand_sub_safe = true,
.expand_mul_safe = true, .expand_mul_safe = true,
@ -107763,6 +107765,8 @@ fn genBody(cg: *CodeGen, body: []const Air.Inst.Index) InnerError!void {
}; };
try res[0].finish(inst, &.{ty_op.operand}, &ops, cg); try res[0].finish(inst, &.{ty_op.operand}, &ops, cg);
}, },
.int_from_float_safe => unreachable,
.int_from_float_optimized_safe => unreachable,
.float_from_int => |air_tag| if (use_old) try cg.airFloatFromInt(inst) else { .float_from_int => |air_tag| if (use_old) try cg.airFloatFromInt(inst) else {
const ty_op = air_datas[@intFromEnum(inst)].ty_op; const ty_op = air_datas[@intFromEnum(inst)].ty_op;
var ops = try cg.tempsFromOperands(inst, .{ty_op.operand}); var ops = try cg.tempsFromOperands(inst, .{ty_op.operand});

View File

@ -23,12 +23,21 @@ const BigIntLimb = std.math.big.Limb;
const BigInt = std.math.big.int; const BigInt = std.math.big.int;
pub fn legalizeFeatures(_: *const std.Target) ?*const Air.Legalize.Features { pub fn legalizeFeatures(_: *const std.Target) ?*const Air.Legalize.Features {
return if (dev.env.supports(.legalize)) comptime &.initMany(&.{ return comptime switch (dev.env.supports(.legalize)) {
.expand_intcast_safe, inline false, true => |supports_legalize| &.init(.{
.expand_add_safe, // we don't currently ask zig1 to use safe optimization modes
.expand_sub_safe, .expand_intcast_safe = supports_legalize,
.expand_mul_safe, .expand_int_from_float_safe = supports_legalize,
}) else null; // we don't currently ask zig1 to use safe optimization modes .expand_int_from_float_optimized_safe = supports_legalize,
.expand_add_safe = supports_legalize,
.expand_sub_safe = supports_legalize,
.expand_mul_safe = supports_legalize,
.expand_packed_load = true,
.expand_packed_store = true,
.expand_packed_struct_field_val = true,
}),
};
} }
/// For most backends, MIR is basically a sequence of machine code instructions, perhaps with some /// For most backends, MIR is basically a sequence of machine code instructions, perhaps with some
@ -3571,6 +3580,8 @@ fn genBodyInner(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail,
.sub_safe, .sub_safe,
.mul_safe, .mul_safe,
.intcast_safe, .intcast_safe,
.int_from_float_safe,
.int_from_float_optimized_safe,
=> return f.fail("TODO implement safety_checked_instructions", .{}), => return f.fail("TODO implement safety_checked_instructions", .{}),
.is_named_enum_value => return f.fail("TODO: C backend: implement is_named_enum_value", .{}), .is_named_enum_value => return f.fail("TODO: C backend: implement is_named_enum_value", .{}),

View File

@ -37,7 +37,10 @@ const compilerRtIntAbbrev = target_util.compilerRtIntAbbrev;
const Error = error{ OutOfMemory, CodegenFail }; const Error = error{ OutOfMemory, CodegenFail };
pub fn legalizeFeatures(_: *const std.Target) ?*const Air.Legalize.Features { pub fn legalizeFeatures(_: *const std.Target) ?*const Air.Legalize.Features {
return null; return comptime &.initMany(&.{
.expand_int_from_float_safe,
.expand_int_from_float_optimized_safe,
});
} }
fn subArchName(target: std.Target, comptime family: std.Target.Cpu.Arch.Family, mappings: anytype) ?[]const u8 { fn subArchName(target: std.Target, comptime family: std.Target.Cpu.Arch.Family, mappings: anytype) ?[]const u8 {
@ -4987,6 +4990,8 @@ pub const FuncGen = struct {
.int_from_float => try self.airIntFromFloat(inst, .normal), .int_from_float => try self.airIntFromFloat(inst, .normal),
.int_from_float_optimized => try self.airIntFromFloat(inst, .fast), .int_from_float_optimized => try self.airIntFromFloat(inst, .fast),
.int_from_float_safe => unreachable, // handled by `legalizeFeatures`
.int_from_float_optimized_safe => unreachable, // handled by `legalizeFeatures`
.array_to_slice => try self.airArrayToSlice(inst), .array_to_slice => try self.airArrayToSlice(inst),
.float_from_int => try self.airFloatFromInt(inst), .float_from_int => try self.airFloatFromInt(inst),

View File

@ -31,6 +31,8 @@ const InstMap = std.AutoHashMapUnmanaged(Air.Inst.Index, IdRef);
pub fn legalizeFeatures(_: *const std.Target) *const Air.Legalize.Features { pub fn legalizeFeatures(_: *const std.Target) *const Air.Legalize.Features {
return comptime &.initMany(&.{ return comptime &.initMany(&.{
.expand_intcast_safe, .expand_intcast_safe,
.expand_int_from_float_safe,
.expand_int_from_float_optimized_safe,
.expand_add_safe, .expand_add_safe,
.expand_sub_safe, .expand_sub_safe,
.expand_mul_safe, .expand_mul_safe,

View File

@ -272,6 +272,15 @@
#define zig_linksection_fn zig_linksection #define zig_linksection_fn zig_linksection
#endif #endif
#if zig_has_attribute(visibility)
#define zig_visibility(name) __attribute__((visibility(#name)))
#else
#define zig_visibility(name) zig_visibility_##name
#define zig_visibility_default
#define zig_visibility_hidden zig_visibility_hidden_unavailable
#define zig_visibility_protected zig_visibility_protected_unavailable
#endif
#if zig_has_builtin(unreachable) || defined(zig_gcc) || defined(zig_tinyc) #if zig_has_builtin(unreachable) || defined(zig_gcc) || defined(zig_tinyc)
#define zig_unreachable() __builtin_unreachable() #define zig_unreachable() __builtin_unreachable()
#elif defined(zig_msvc) #elif defined(zig_msvc)

Binary file not shown.

View File

@ -102,6 +102,7 @@ test "comptime_int @floatFromInt" {
test "@floatFromInt" { test "@floatFromInt" {
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
const S = struct { const S = struct {
@ -2737,3 +2738,66 @@ test "peer type resolution: slice of sentinel-terminated array" {
try expect(result[0][0] == 10); try expect(result[0][0] == 10);
try expect(result[0][1] == 20); try expect(result[0][1] == 20);
} }
test "@intFromFloat boundary cases" {
if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
const S = struct {
fn case(comptime I: type, x: f32, bump: enum { up, down }, expected: I) !void {
const input: f32 = switch (bump) {
.up => std.math.nextAfter(f32, x, std.math.inf(f32)),
.down => std.math.nextAfter(f32, x, -std.math.inf(f32)),
};
const output: I = @intFromFloat(input);
try expect(output == expected);
}
fn doTheTest() !void {
try case(u8, 256.0, .down, 255);
try case(u8, -1.0, .up, 0);
try case(i8, 128.0, .down, 127);
try case(i8, -129.0, .up, -128);
try case(u0, 1.0, .down, 0);
try case(u0, -1.0, .up, 0);
try case(i0, 1.0, .down, 0);
try case(i0, -1.0, .up, 0);
try case(u10, 1024.0, .down, 1023);
try case(u10, -1.0, .up, 0);
try case(i10, 512.0, .down, 511);
try case(i10, -513.0, .up, -512);
}
};
try S.doTheTest();
try comptime S.doTheTest();
}
test "@intFromFloat vector boundary cases" {
if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest;
if (builtin.zig_backend == .stage2_x86_64 and builtin.target.ofmt != .elf and builtin.target.ofmt != .macho) return error.SkipZigTest;
const S = struct {
fn case(comptime I: type, unshifted_inputs: [2]f32, expected: [2]I) !void {
const inputs: @Vector(2, f32) = .{
std.math.nextAfter(f32, unshifted_inputs[0], std.math.inf(f32)),
std.math.nextAfter(f32, unshifted_inputs[1], -std.math.inf(f32)),
};
const outputs: @Vector(2, I) = @intFromFloat(inputs);
try expect(outputs[0] == expected[0]);
try expect(outputs[1] == expected[1]);
}
fn doTheTest() !void {
try case(u8, .{ -1.0, 256.0 }, .{ 0, 255 });
try case(i8, .{ -129.0, 128.0 }, .{ -128, 127 });
try case(u0, .{ -1.0, 1.0 }, .{ 0, 0 });
try case(i0, .{ -1.0, 1.0 }, .{ 0, 0 });
try case(u10, .{ -1.0, 1024.0 }, .{ 0, 1023 });
try case(i10, .{ -513.0, 512.0 }, .{ -512, 511 });
}
};
try S.doTheTest();
try comptime S.doTheTest();
}

View File

@ -0,0 +1,16 @@
const std = @import("std");
pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace, _: ?usize) noreturn {
_ = stack_trace;
if (std.mem.eql(u8, message, "integer part of floating point value out of bounds")) {
std.process.exit(0);
}
std.process.exit(1);
}
var x: f32 = 1.0;
pub fn main() !void {
_ = @as(i0, @intFromFloat(x));
return error.TestFailed;
}
// run
// backend=stage2,llvm
// target=native

View File

@ -0,0 +1,16 @@
const std = @import("std");
pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace, _: ?usize) noreturn {
_ = stack_trace;
if (std.mem.eql(u8, message, "integer part of floating point value out of bounds")) {
std.process.exit(0);
}
std.process.exit(1);
}
var x: f32 = -1.0;
pub fn main() !void {
_ = @as(i0, @intFromFloat(x));
return error.TestFailed;
}
// run
// backend=stage2,llvm
// target=native

View File

@ -0,0 +1,16 @@
const std = @import("std");
pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace, _: ?usize) noreturn {
_ = stack_trace;
if (std.mem.eql(u8, message, "integer part of floating point value out of bounds")) {
std.process.exit(0);
}
std.process.exit(1);
}
var x: f32 = 128;
pub fn main() !void {
_ = @as(i8, @intFromFloat(x));
return error.TestFailed;
}
// run
// backend=stage2,llvm
// target=native

View File

@ -0,0 +1,16 @@
const std = @import("std");
pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace, _: ?usize) noreturn {
_ = stack_trace;
if (std.mem.eql(u8, message, "integer part of floating point value out of bounds")) {
std.process.exit(0);
}
std.process.exit(1);
}
var x: f32 = -129;
pub fn main() !void {
_ = @as(i8, @intFromFloat(x));
return error.TestFailed;
}
// run
// backend=stage2,llvm
// target=native

View File

@ -0,0 +1,16 @@
const std = @import("std");
pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace, _: ?usize) noreturn {
_ = stack_trace;
if (std.mem.eql(u8, message, "integer part of floating point value out of bounds")) {
std.process.exit(0);
}
std.process.exit(1);
}
var x: f32 = 1.0;
pub fn main() !void {
_ = @as(u0, @intFromFloat(x));
return error.TestFailed;
}
// run
// backend=stage2,llvm
// target=native

View File

@ -0,0 +1,16 @@
const std = @import("std");
pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace, _: ?usize) noreturn {
_ = stack_trace;
if (std.mem.eql(u8, message, "integer part of floating point value out of bounds")) {
std.process.exit(0);
}
std.process.exit(1);
}
var x: f32 = -1.0;
pub fn main() !void {
_ = @as(u0, @intFromFloat(x));
return error.TestFailed;
}
// run
// backend=stage2,llvm
// target=native

View File

@ -0,0 +1,16 @@
const std = @import("std");
pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace, _: ?usize) noreturn {
_ = stack_trace;
if (std.mem.eql(u8, message, "integer part of floating point value out of bounds")) {
std.process.exit(0);
}
std.process.exit(1);
}
var x: f32 = 256;
pub fn main() !void {
_ = @as(u8, @intFromFloat(x));
return error.TestFailed;
}
// run
// backend=stage2,llvm
// target=native

View File

@ -0,0 +1,16 @@
const std = @import("std");
pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace, _: ?usize) noreturn {
_ = stack_trace;
if (std.mem.eql(u8, message, "integer part of floating point value out of bounds")) {
std.process.exit(0);
}
std.process.exit(1);
}
var x: f32 = -1;
pub fn main() !void {
_ = @as(u8, @intFromFloat(x));
return error.TestFailed;
}
// run
// backend=stage2,llvm
// target=native

View File

@ -0,0 +1,16 @@
const std = @import("std");
pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace, _: ?usize) noreturn {
_ = stack_trace;
if (std.mem.eql(u8, message, "integer part of floating point value out of bounds")) {
std.process.exit(0);
}
std.process.exit(1);
}
var x: @Vector(2, f32) = .{ 100, 512 };
pub fn main() !void {
_ = @as(@Vector(2, i10), @intFromFloat(x));
return error.TestFailed;
}
// run
// backend=stage2,llvm
// target=native

View File

@ -0,0 +1,16 @@
const std = @import("std");
pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace, _: ?usize) noreturn {
_ = stack_trace;
if (std.mem.eql(u8, message, "integer part of floating point value out of bounds")) {
std.process.exit(0);
}
std.process.exit(1);
}
var x: @Vector(2, f32) = .{ 100, -513 };
pub fn main() !void {
_ = @as(@Vector(2, i10), @intFromFloat(x));
return error.TestFailed;
}
// run
// backend=stage2,llvm
// target=native