authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2022-07-13 13:14:37-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2022-07-13 13:14:37-07:00
log92bc3cbe27792be0300fb5f104c011a11f3cf40f
tree8d9e7f2df29a824d104cfa01d2eb832d456554aa
parent35e70111248f795fbdcefd5ae0d6fc494d1b0683

stage2: fix comptime bitcast involving f80

* Sema: implement comptime bitcast of f80 with integer-like types bitwise rather than taking a round trip through memory layout. * Type: introduce `isAbiInt`. * Value: comptime memory write of f80 writes 0 bytes for padding instead of leaving the memory uninitialized. * Value: floatReadFromMemory has a more general implementation, checking the endianness rather than checking for specific architectures. This fixes behavior test failures occurring on MIPS.

4 files changed, 64 insertions(+), 15 deletions(-)

lib/std/math/float.zig+5-5
......@@ -9,14 +9,14 @@ inline fn mantissaOne(comptime T: type) comptime_int {
99
1010/// Creates floating point type T from an unbiased exponent and raw mantissa.
1111inline fn reconstructFloat(comptime T: type, exponent: comptime_int, mantissa: comptime_int) T {
12 const TBits = std.meta.Int(.unsigned, @bitSizeOf(T));
12 const TBits = @Type(.{ .Int = .{ .signedness = .unsigned, .bits = @bitSizeOf(T) } });
1313 const biased_exponent = @as(TBits, exponent + floatExponentMax(T));
1414 return @bitCast(T, (biased_exponent << floatMantissaBits(T)) | @as(TBits, mantissa));
1515}
1616
1717/// Returns the number of bits in the exponent of floating point type T.
1818pub inline fn floatExponentBits(comptime T: type) comptime_int {
19 assert(@typeInfo(T) == .Float);
19 comptime assert(@typeInfo(T) == .Float);
2020
2121 return switch (@typeInfo(T).Float.bits) {
2222 16 => 5,
......@@ -30,7 +30,7 @@ pub inline fn floatExponentBits(comptime T: type) comptime_int {
3030
3131/// Returns the number of bits in the mantissa of floating point type T.
3232pub inline fn floatMantissaBits(comptime T: type) comptime_int {
33 assert(@typeInfo(T) == .Float);
33 comptime assert(@typeInfo(T) == .Float);
3434
3535 return switch (@typeInfo(T).Float.bits) {
3636 16 => 10,
......@@ -44,7 +44,7 @@ pub inline fn floatMantissaBits(comptime T: type) comptime_int {
4444
4545/// Returns the number of fractional bits in the mantissa of floating point type T.
4646pub inline fn floatFractionalBits(comptime T: type) comptime_int {
47 assert(@typeInfo(T) == .Float);
47 comptime assert(@typeInfo(T) == .Float);
4848
4949 // standard IEEE floats have an implicit 0.m or 1.m integer part
5050 // f80 is special and has an explicitly stored bit in the MSB
......@@ -97,7 +97,7 @@ pub inline fn inf(comptime T: type) T {
9797 return reconstructFloat(T, floatExponentMax(T) + 1, mantissaOne(T));
9898}
9999
100test "std.math.float" {
100test "float bits" {
101101 inline for ([_]type{ f16, f32, f64, f80, f128, c_longdouble }) |T| {
102102 // (1 +) for the sign bit, since it is separate from the other bits
103103 const size = 1 + floatExponentBits(T) + floatMantissaBits(T);
src/Sema.zig+42
......@@ -22571,6 +22571,48 @@ fn bitCastVal(
2257122571 const target = sema.mod.getTarget();
2257222572 if (old_ty.eql(new_ty, sema.mod)) return val;
2257322573
22574 // Some conversions have a bitwise definition that ignores in-memory layout,
22575 // such as converting between f80 and u80.
22576
22577 if (old_ty.eql(Type.f80, sema.mod) and new_ty.isAbiInt()) {
22578 const float = val.toFloat(f80);
22579 switch (new_ty.intInfo(target).signedness) {
22580 .signed => {
22581 const int = @bitCast(i80, float);
22582 const limbs = try sema.arena.alloc(std.math.big.Limb, 2);
22583 const big_int = std.math.big.int.Mutable.init(limbs, int);
22584 return Value.fromBigInt(sema.arena, big_int.toConst());
22585 },
22586 .unsigned => {
22587 const int = @bitCast(u80, float);
22588 const limbs = try sema.arena.alloc(std.math.big.Limb, 2);
22589 const big_int = std.math.big.int.Mutable.init(limbs, int);
22590 return Value.fromBigInt(sema.arena, big_int.toConst());
22591 },
22592 }
22593 }
22594
22595 if (new_ty.eql(Type.f80, sema.mod) and old_ty.isAbiInt()) {
22596 var bigint_space: Value.BigIntSpace = undefined;
22597 var bigint = try val.toBigIntAdvanced(&bigint_space, target, sema.kit(block, src));
22598 switch (old_ty.intInfo(target).signedness) {
22599 .signed => {
22600 // This conversion cannot fail because we already checked bit size before
22601 // calling bitCastVal.
22602 const int = bigint.to(i80) catch unreachable;
22603 const float = @bitCast(f80, int);
22604 return Value.Tag.float_80.create(sema.arena, float);
22605 },
22606 .unsigned => {
22607 // This conversion cannot fail because we already checked bit size before
22608 // calling bitCastVal.
22609 const int = bigint.to(u80) catch unreachable;
22610 const float = @bitCast(f80, int);
22611 return Value.Tag.float_80.create(sema.arena, float);
22612 },
22613 }
22614 }
22615
2257422616 // For types with well-defined memory layouts, we serialize them a byte buffer,
2257522617 // then deserialize to the new type.
2257622618 const abi_size = try sema.usizeCast(block, src, old_ty.abiSize(target));
src/type.zig+10
......@@ -4439,6 +4439,16 @@ pub const Type = extern union {
44394439 };
44404440 }
44414441
4442 /// Returns true for integers, enums, error sets, and packed structs.
4443 /// If this function returns true, then intInfo() can be called on the type.
4444 pub fn isAbiInt(ty: Type) bool {
4445 return switch (ty.zigTypeTag()) {
4446 .Int, .Enum, .ErrorSet => true,
4447 .Struct => ty.containerLayout() == .Packed,
4448 else => false,
4449 };
4450 }
4451
44424452 /// Asserts the type is an integer, enum, error set, or vector of one of them.
44434453 pub fn intInfo(self: Type, target: Target) struct { signedness: std.builtin.Signedness, bits: u16 } {
44444454 var ty = self;
src/value.zig+7-10
......@@ -1468,8 +1468,7 @@ pub const Value = extern union {
14681468 const repr = std.math.break_f80(f);
14691469 std.mem.writeInt(u64, buffer[0..8], repr.fraction, endian);
14701470 std.mem.writeInt(u16, buffer[8..10], repr.exp, endian);
1471 // TODO set the rest of the bytes to undefined. should we use 0xaa
1472 // or is there a different way?
1471 std.mem.set(u8, buffer[10..], 0);
14731472 return;
14741473 }
14751474 const Int = @Type(.{ .Int = .{
......@@ -1481,20 +1480,18 @@ pub const Value = extern union {
14811480 }
14821481
14831482 fn floatReadFromMemory(comptime F: type, target: Target, buffer: []const u8) F {
1483 const endian = target.cpu.arch.endian();
14841484 if (F == f80) {
1485 switch (target.cpu.arch) {
1486 .i386, .x86_64 => return std.math.make_f80(.{
1487 .fraction = std.mem.readIntLittle(u64, buffer[0..8]),
1488 .exp = std.mem.readIntLittle(u16, buffer[8..10]),
1489 }),
1490 else => {},
1491 }
1485 return std.math.make_f80(.{
1486 .fraction = readInt(u64, buffer[0..8], endian),
1487 .exp = readInt(u16, buffer[8..10], endian),
1488 });
14921489 }
14931490 const Int = @Type(.{ .Int = .{
14941491 .signedness = .unsigned,
14951492 .bits = @typeInfo(F).Float.bits,
14961493 } });
1497 const int = readInt(Int, buffer[0..@sizeOf(Int)], target.cpu.arch.endian());
1494 const int = readInt(Int, buffer[0..@sizeOf(Int)], endian);
14981495 return @bitCast(F, int);
14991496 }
15001497