| author | |
| committer | |
| log | 92bc3cbe27792be0300fb5f104c011a11f3cf40f |
| tree | 8d9e7f2df29a824d104cfa01d2eb832d456554aa |
| parent | 35e70111248f795fbdcefd5ae0d6fc494d1b0683 |
* 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 { | ... | @@ -9,14 +9,14 @@ inline fn mantissaOne(comptime T: type) comptime_int { |
| 9 | 9 | ||
| 10 | /// Creates floating point type T from an unbiased exponent and raw mantissa. | 10 | /// Creates floating point type T from an unbiased exponent and raw mantissa. |
| 11 | inline fn reconstructFloat(comptime T: type, exponent: comptime_int, mantissa: comptime_int) T { | 11 | inline 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) } }); |
| 13 | const biased_exponent = @as(TBits, exponent + floatExponentMax(T)); | 13 | const biased_exponent = @as(TBits, exponent + floatExponentMax(T)); |
| 14 | return @bitCast(T, (biased_exponent << floatMantissaBits(T)) | @as(TBits, mantissa)); | 14 | return @bitCast(T, (biased_exponent << floatMantissaBits(T)) | @as(TBits, mantissa)); |
| 15 | } | 15 | } |
| 16 | 16 | ||
| 17 | /// Returns the number of bits in the exponent of floating point type T. | 17 | /// Returns the number of bits in the exponent of floating point type T. |
| 18 | pub inline fn floatExponentBits(comptime T: type) comptime_int { | 18 | pub inline fn floatExponentBits(comptime T: type) comptime_int { |
| 19 | assert(@typeInfo(T) == .Float); | 19 | comptime assert(@typeInfo(T) == .Float); |
| 20 | 20 | ||
| 21 | return switch (@typeInfo(T).Float.bits) { | 21 | return switch (@typeInfo(T).Float.bits) { |
| 22 | 16 => 5, | 22 | 16 => 5, |
| ... | @@ -30,7 +30,7 @@ pub inline fn floatExponentBits(comptime T: type) comptime_int { | ... | @@ -30,7 +30,7 @@ pub inline fn floatExponentBits(comptime T: type) comptime_int { |
| 30 | 30 | ||
| 31 | /// Returns the number of bits in the mantissa of floating point type T. | 31 | /// Returns the number of bits in the mantissa of floating point type T. |
| 32 | pub inline fn floatMantissaBits(comptime T: type) comptime_int { | 32 | pub inline fn floatMantissaBits(comptime T: type) comptime_int { |
| 33 | assert(@typeInfo(T) == .Float); | 33 | comptime assert(@typeInfo(T) == .Float); |
| 34 | 34 | ||
| 35 | return switch (@typeInfo(T).Float.bits) { | 35 | return switch (@typeInfo(T).Float.bits) { |
| 36 | 16 => 10, | 36 | 16 => 10, |
| ... | @@ -44,7 +44,7 @@ pub inline fn floatMantissaBits(comptime T: type) comptime_int { | ... | @@ -44,7 +44,7 @@ pub inline fn floatMantissaBits(comptime T: type) comptime_int { |
| 44 | 44 | ||
| 45 | /// Returns the number of fractional bits in the mantissa of floating point type T. | 45 | /// Returns the number of fractional bits in the mantissa of floating point type T. |
| 46 | pub inline fn floatFractionalBits(comptime T: type) comptime_int { | 46 | pub inline fn floatFractionalBits(comptime T: type) comptime_int { |
| 47 | assert(@typeInfo(T) == .Float); | 47 | comptime assert(@typeInfo(T) == .Float); |
| 48 | 48 | ||
| 49 | // standard IEEE floats have an implicit 0.m or 1.m integer part | 49 | // standard IEEE floats have an implicit 0.m or 1.m integer part |
| 50 | // f80 is special and has an explicitly stored bit in the MSB | 50 | // f80 is special and has an explicitly stored bit in the MSB |
| ... | @@ -97,7 +97,7 @@ pub inline fn inf(comptime T: type) T { | ... | @@ -97,7 +97,7 @@ pub inline fn inf(comptime T: type) T { |
| 97 | return reconstructFloat(T, floatExponentMax(T) + 1, mantissaOne(T)); | 97 | return reconstructFloat(T, floatExponentMax(T) + 1, mantissaOne(T)); |
| 98 | } | 98 | } |
| 99 | 99 | ||
| 100 | test "std.math.float" { | 100 | test "float bits" { |
| 101 | inline for ([_]type{ f16, f32, f64, f80, f128, c_longdouble }) |T| { | 101 | inline for ([_]type{ f16, f32, f64, f80, f128, c_longdouble }) |T| { |
| 102 | // (1 +) for the sign bit, since it is separate from the other bits | 102 | // (1 +) for the sign bit, since it is separate from the other bits |
| 103 | const size = 1 + floatExponentBits(T) + floatMantissaBits(T); | 103 | const size = 1 + floatExponentBits(T) + floatMantissaBits(T); |
src/Sema.zig+42| ... | @@ -22571,6 +22571,48 @@ fn bitCastVal( | ... | @@ -22571,6 +22571,48 @@ fn bitCastVal( |
| 22571 | const target = sema.mod.getTarget(); | 22571 | const target = sema.mod.getTarget(); |
| 22572 | if (old_ty.eql(new_ty, sema.mod)) return val; | 22572 | if (old_ty.eql(new_ty, sema.mod)) return val; |
| 22573 | 22573 | ||
| 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 | |||
| 22574 | // For types with well-defined memory layouts, we serialize them a byte buffer, | 22616 | // For types with well-defined memory layouts, we serialize them a byte buffer, |
| 22575 | // then deserialize to the new type. | 22617 | // then deserialize to the new type. |
| 22576 | const abi_size = try sema.usizeCast(block, src, old_ty.abiSize(target)); | 22618 | 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 { | ... | @@ -4439,6 +4439,16 @@ pub const Type = extern union { |
| 4439 | }; | 4439 | }; |
| 4440 | } | 4440 | } |
| 4441 | 4441 | ||
| 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 | |||
| 4442 | /// Asserts the type is an integer, enum, error set, or vector of one of them. | 4452 | /// Asserts the type is an integer, enum, error set, or vector of one of them. |
| 4443 | pub fn intInfo(self: Type, target: Target) struct { signedness: std.builtin.Signedness, bits: u16 } { | 4453 | pub fn intInfo(self: Type, target: Target) struct { signedness: std.builtin.Signedness, bits: u16 } { |
| 4444 | var ty = self; | 4454 | var ty = self; |
src/value.zig+7-10| ... | @@ -1468,8 +1468,7 @@ pub const Value = extern union { | ... | @@ -1468,8 +1468,7 @@ pub const Value = extern union { |
| 1468 | const repr = std.math.break_f80(f); | 1468 | const repr = std.math.break_f80(f); |
| 1469 | std.mem.writeInt(u64, buffer[0..8], repr.fraction, endian); | 1469 | std.mem.writeInt(u64, buffer[0..8], repr.fraction, endian); |
| 1470 | std.mem.writeInt(u16, buffer[8..10], repr.exp, endian); | 1470 | std.mem.writeInt(u16, buffer[8..10], repr.exp, endian); |
| 1471 | // TODO set the rest of the bytes to undefined. should we use 0xaa | 1471 | std.mem.set(u8, buffer[10..], 0); |
| 1472 | // or is there a different way? | ||
| 1473 | return; | 1472 | return; |
| 1474 | } | 1473 | } |
| 1475 | const Int = @Type(.{ .Int = .{ | 1474 | const Int = @Type(.{ .Int = .{ |
| ... | @@ -1481,20 +1480,18 @@ pub const Value = extern union { | ... | @@ -1481,20 +1480,18 @@ pub const Value = extern union { |
| 1481 | } | 1480 | } |
| 1482 | 1481 | ||
| 1483 | fn floatReadFromMemory(comptime F: type, target: Target, buffer: []const u8) F { | 1482 | fn floatReadFromMemory(comptime F: type, target: Target, buffer: []const u8) F { |
| 1483 | const endian = target.cpu.arch.endian(); | ||
| 1484 | if (F == f80) { | 1484 | if (F == f80) { |
| 1485 | switch (target.cpu.arch) { | 1485 | return std.math.make_f80(.{ |
| 1486 | .i386, .x86_64 => return std.math.make_f80(.{ | 1486 | .fraction = readInt(u64, buffer[0..8], endian), |
| 1487 | .fraction = std.mem.readIntLittle(u64, buffer[0..8]), | 1487 | .exp = readInt(u16, buffer[8..10], endian), |
| 1488 | .exp = std.mem.readIntLittle(u16, buffer[8..10]), | 1488 | }); |
| 1489 | }), | ||
| 1490 | else => {}, | ||
| 1491 | } | ||
| 1492 | } | 1489 | } |
| 1493 | const Int = @Type(.{ .Int = .{ | 1490 | const Int = @Type(.{ .Int = .{ |
| 1494 | .signedness = .unsigned, | 1491 | .signedness = .unsigned, |
| 1495 | .bits = @typeInfo(F).Float.bits, | 1492 | .bits = @typeInfo(F).Float.bits, |
| 1496 | } }); | 1493 | } }); |
| 1497 | const int = readInt(Int, buffer[0..@sizeOf(Int)], target.cpu.arch.endian()); | 1494 | const int = readInt(Int, buffer[0..@sizeOf(Int)], endian); |
| 1498 | return @bitCast(F, int); | 1495 | return @bitCast(F, int); |
| 1499 | } | 1496 | } |
| 1500 | 1497 |