Commit 92bc3cbe27

Andrew Kelley <andrew@ziglang.org>
2022-07-13 22:14:37
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.
1 parent 35e7011
Changed files (4)
lib/std/math/float.zig
@@ -9,14 +9,14 @@ inline fn mantissaOne(comptime T: type) comptime_int {
 
 /// Creates floating point type T from an unbiased exponent and raw mantissa.
 inline fn reconstructFloat(comptime T: type, exponent: comptime_int, mantissa: comptime_int) T {
-    const TBits = std.meta.Int(.unsigned, @bitSizeOf(T));
+    const TBits = @Type(.{ .Int = .{ .signedness = .unsigned, .bits = @bitSizeOf(T) } });
     const biased_exponent = @as(TBits, exponent + floatExponentMax(T));
     return @bitCast(T, (biased_exponent << floatMantissaBits(T)) | @as(TBits, mantissa));
 }
 
 /// Returns the number of bits in the exponent of floating point type T.
 pub inline fn floatExponentBits(comptime T: type) comptime_int {
-    assert(@typeInfo(T) == .Float);
+    comptime assert(@typeInfo(T) == .Float);
 
     return switch (@typeInfo(T).Float.bits) {
         16 => 5,
@@ -30,7 +30,7 @@ pub inline fn floatExponentBits(comptime T: type) comptime_int {
 
 /// Returns the number of bits in the mantissa of floating point type T.
 pub inline fn floatMantissaBits(comptime T: type) comptime_int {
-    assert(@typeInfo(T) == .Float);
+    comptime assert(@typeInfo(T) == .Float);
 
     return switch (@typeInfo(T).Float.bits) {
         16 => 10,
@@ -44,7 +44,7 @@ pub inline fn floatMantissaBits(comptime T: type) comptime_int {
 
 /// Returns the number of fractional bits in the mantissa of floating point type T.
 pub inline fn floatFractionalBits(comptime T: type) comptime_int {
-    assert(@typeInfo(T) == .Float);
+    comptime assert(@typeInfo(T) == .Float);
 
     // standard IEEE floats have an implicit 0.m or 1.m integer part
     // f80 is special and has an explicitly stored bit in the MSB
@@ -97,7 +97,7 @@ pub inline fn inf(comptime T: type) T {
     return reconstructFloat(T, floatExponentMax(T) + 1, mantissaOne(T));
 }
 
-test "std.math.float" {
+test "float bits" {
     inline for ([_]type{ f16, f32, f64, f80, f128, c_longdouble }) |T| {
         // (1 +) for the sign bit, since it is separate from the other bits
         const size = 1 + floatExponentBits(T) + floatMantissaBits(T);
src/Sema.zig
@@ -22571,6 +22571,48 @@ fn bitCastVal(
     const target = sema.mod.getTarget();
     if (old_ty.eql(new_ty, sema.mod)) return val;
 
+    // Some conversions have a bitwise definition that ignores in-memory layout,
+    // such as converting between f80 and u80.
+
+    if (old_ty.eql(Type.f80, sema.mod) and new_ty.isAbiInt()) {
+        const float = val.toFloat(f80);
+        switch (new_ty.intInfo(target).signedness) {
+            .signed => {
+                const int = @bitCast(i80, float);
+                const limbs = try sema.arena.alloc(std.math.big.Limb, 2);
+                const big_int = std.math.big.int.Mutable.init(limbs, int);
+                return Value.fromBigInt(sema.arena, big_int.toConst());
+            },
+            .unsigned => {
+                const int = @bitCast(u80, float);
+                const limbs = try sema.arena.alloc(std.math.big.Limb, 2);
+                const big_int = std.math.big.int.Mutable.init(limbs, int);
+                return Value.fromBigInt(sema.arena, big_int.toConst());
+            },
+        }
+    }
+
+    if (new_ty.eql(Type.f80, sema.mod) and old_ty.isAbiInt()) {
+        var bigint_space: Value.BigIntSpace = undefined;
+        var bigint = try val.toBigIntAdvanced(&bigint_space, target, sema.kit(block, src));
+        switch (old_ty.intInfo(target).signedness) {
+            .signed => {
+                // This conversion cannot fail because we already checked bit size before
+                // calling bitCastVal.
+                const int = bigint.to(i80) catch unreachable;
+                const float = @bitCast(f80, int);
+                return Value.Tag.float_80.create(sema.arena, float);
+            },
+            .unsigned => {
+                // This conversion cannot fail because we already checked bit size before
+                // calling bitCastVal.
+                const int = bigint.to(u80) catch unreachable;
+                const float = @bitCast(f80, int);
+                return Value.Tag.float_80.create(sema.arena, float);
+            },
+        }
+    }
+
     // For types with well-defined memory layouts, we serialize them a byte buffer,
     // then deserialize to the new type.
     const abi_size = try sema.usizeCast(block, src, old_ty.abiSize(target));
src/type.zig
@@ -4439,6 +4439,16 @@ pub const Type = extern union {
         };
     }
 
+    /// Returns true for integers, enums, error sets, and packed structs.
+    /// If this function returns true, then intInfo() can be called on the type.
+    pub fn isAbiInt(ty: Type) bool {
+        return switch (ty.zigTypeTag()) {
+            .Int, .Enum, .ErrorSet => true,
+            .Struct => ty.containerLayout() == .Packed,
+            else => false,
+        };
+    }
+
     /// Asserts the type is an integer, enum, error set, or vector of one of them.
     pub fn intInfo(self: Type, target: Target) struct { signedness: std.builtin.Signedness, bits: u16 } {
         var ty = self;
src/value.zig
@@ -1468,8 +1468,7 @@ pub const Value = extern union {
             const repr = std.math.break_f80(f);
             std.mem.writeInt(u64, buffer[0..8], repr.fraction, endian);
             std.mem.writeInt(u16, buffer[8..10], repr.exp, endian);
-            // TODO set the rest of the bytes to undefined. should we use 0xaa
-            // or is there a different way?
+            std.mem.set(u8, buffer[10..], 0);
             return;
         }
         const Int = @Type(.{ .Int = .{
@@ -1481,20 +1480,18 @@ pub const Value = extern union {
     }
 
     fn floatReadFromMemory(comptime F: type, target: Target, buffer: []const u8) F {
+        const endian = target.cpu.arch.endian();
         if (F == f80) {
-            switch (target.cpu.arch) {
-                .i386, .x86_64 => return std.math.make_f80(.{
-                    .fraction = std.mem.readIntLittle(u64, buffer[0..8]),
-                    .exp = std.mem.readIntLittle(u16, buffer[8..10]),
-                }),
-                else => {},
-            }
+            return std.math.make_f80(.{
+                .fraction = readInt(u64, buffer[0..8], endian),
+                .exp = readInt(u16, buffer[8..10], endian),
+            });
         }
         const Int = @Type(.{ .Int = .{
             .signedness = .unsigned,
             .bits = @typeInfo(F).Float.bits,
         } });
-        const int = readInt(Int, buffer[0..@sizeOf(Int)], target.cpu.arch.endian());
+        const int = readInt(Int, buffer[0..@sizeOf(Int)], endian);
         return @bitCast(F, int);
     }