From f9481402f0c6f2eb4618bb0385aff18c12912c26 Mon Sep 17 00:00:00 2001 From: LemonBoy Date: Thu, 15 Apr 2021 18:12:11 +0200 Subject: [PATCH 1/7] stage1: Fix negation for zero floating point values Toggling the sign by computing 0-x doesn't really work for zero values. --- src/stage1/bigfloat.cpp | 9 +++------ src/stage1/ir.cpp | 11 +++-------- src/stage1/softfloat_ext.cpp | 13 +++++++++++++ src/stage1/softfloat_ext.hpp | 3 +++ 4 files changed, 22 insertions(+), 14 deletions(-) diff --git a/src/stage1/bigfloat.cpp b/src/stage1/bigfloat.cpp index 58b0aff54a..840cdccc8b 100644 --- a/src/stage1/bigfloat.cpp +++ b/src/stage1/bigfloat.cpp @@ -9,6 +9,7 @@ #include "bigint.hpp" #include "buffer.hpp" #include "softfloat.hpp" +#include "softfloat_ext.hpp" #include "parse_f128.h" #include #include @@ -60,9 +61,7 @@ void bigfloat_init_bigint(BigFloat *dest, const BigInt *op) { if (i == 0) { if (op->is_negative) { - float128_t zero_f128; - ui32_to_f128M(0, &zero_f128); - f128M_sub(&zero_f128, &dest->value, &dest->value); + f128M_neg(&dest->value, &dest->value); } return; } @@ -89,9 +88,7 @@ void bigfloat_add(BigFloat *dest, const BigFloat *op1, const BigFloat *op2) { } void bigfloat_negate(BigFloat *dest, const BigFloat *op) { - float128_t zero_f128; - ui32_to_f128M(0, &zero_f128); - f128M_sub(&zero_f128, &op->value, &dest->value); + f128M_neg(&op->value, &dest->value); } void bigfloat_sub(BigFloat *dest, const BigFloat *op1, const BigFloat *op2) { diff --git a/src/stage1/ir.cpp b/src/stage1/ir.cpp index 71a233c964..942b6028d9 100644 --- a/src/stage1/ir.cpp +++ b/src/stage1/ir.cpp @@ -11363,11 +11363,8 @@ static void float_negate(ZigValue *out_val, ZigValue *op) { } else if (op->type->id == ZigTypeIdFloat) { switch (op->type->data.floating.bit_count) { case 16: - { - const float16_t zero = zig_double_to_f16(0); - out_val->data.x_f16 = f16_sub(zero, op->data.x_f16); - return; - } + out_val->data.x_f16 = f16_neg(op->data.x_f16); + return; case 32: out_val->data.x_f32 = -op->data.x_f32; return; @@ -11375,9 +11372,7 @@ static void float_negate(ZigValue *out_val, ZigValue *op) { out_val->data.x_f64 = -op->data.x_f64; return; case 128: - float128_t zero_f128; - ui32_to_f128M(0, &zero_f128); - f128M_sub(&zero_f128, &op->data.x_f128, &out_val->data.x_f128); + f128M_neg(&op->data.x_f128, &out_val->data.x_f128); return; default: zig_unreachable(); diff --git a/src/stage1/softfloat_ext.cpp b/src/stage1/softfloat_ext.cpp index 8408a15116..0eebe7365e 100644 --- a/src/stage1/softfloat_ext.cpp +++ b/src/stage1/softfloat_ext.cpp @@ -1,6 +1,8 @@ #include "softfloat_ext.hpp" extern "C" { + #include "platform.h" + #include "internals.h" #include "softfloat.h" } @@ -22,4 +24,15 @@ void f128M_trunc(const float128_t *aPtr, float128_t *zPtr) { } else { f128M_roundToInt(aPtr, softfloat_round_min, false, zPtr); } +} + +float16_t f16_neg(const float16_t a) { + union ui16_f16 uZ; + uZ.ui = a.v ^ (UINT16_C(1) << 15); + return uZ.f; +} + +void f128M_neg(const float128_t *aPtr, float128_t *zPtr) { + zPtr->v[indexWord(2,1)] = aPtr->v[indexWord(2,1)] ^ (UINT64_C(1) << 63); + zPtr->v[indexWord(2,0)] = aPtr->v[indexWord(2,0)]; } \ No newline at end of file diff --git a/src/stage1/softfloat_ext.hpp b/src/stage1/softfloat_ext.hpp index 0a1f958933..42922a5226 100644 --- a/src/stage1/softfloat_ext.hpp +++ b/src/stage1/softfloat_ext.hpp @@ -5,5 +5,8 @@ void f128M_abs(const float128_t *aPtr, float128_t *zPtr); void f128M_trunc(const float128_t *aPtr, float128_t *zPtr); +void f128M_neg(const float128_t *aPtr, float128_t *zPtr); + +float16_t f16_neg(const float16_t a); #endif \ No newline at end of file From 8e0b2f0e52b195933ae7df9d66f8763fd53b0281 Mon Sep 17 00:00:00 2001 From: LemonBoy Date: Thu, 15 Apr 2021 18:14:25 +0200 Subject: [PATCH 2/7] compiler-rt: Fix typo in implementation of fp truncation ops The problem went unnoticed for years, yay. --- lib/std/special/compiler_rt/truncXfYf2.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/std/special/compiler_rt/truncXfYf2.zig b/lib/std/special/compiler_rt/truncXfYf2.zig index 470ac17c2c..cae5644962 100644 --- a/lib/std/special/compiler_rt/truncXfYf2.zig +++ b/lib/std/special/compiler_rt/truncXfYf2.zig @@ -122,7 +122,7 @@ fn truncXfYf2(comptime dst_t: type, comptime src_t: type, a: src_t) dst_t { if (shift > srcSigBits) { absResult = 0; } else { - const sticky: src_rep_t = significand << @intCast(SrcShift, srcBits - shift); + const sticky: src_rep_t = @boolToInt(significand << @intCast(SrcShift, srcBits - shift) != 0); const denormalizedSignificand: src_rep_t = significand >> @intCast(SrcShift, shift) | sticky; absResult = @intCast(dst_rep_t, denormalizedSignificand >> (srcSigBits - dstSigBits)); const roundBits: src_rep_t = denormalizedSignificand & roundMask; From 2d00f17d155b46e8effb47c363e051f50b866d47 Mon Sep 17 00:00:00 2001 From: LemonBoy Date: Thu, 15 Apr 2021 18:15:44 +0200 Subject: [PATCH 3/7] test: Add test to ensure signed zeros are properly computed Ensure everything's ok at comptime and runtime. --- test/stage1/behavior/math.zig | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/test/stage1/behavior/math.zig b/test/stage1/behavior/math.zig index 68690c9af8..3835aab0f4 100644 --- a/test/stage1/behavior/math.zig +++ b/test/stage1/behavior/math.zig @@ -843,3 +843,20 @@ test "compare undefined literal with comptime_int" { x = true; expect(x); } + +test "signed zeros are represented properly" { + const S = struct { + fn doTheTest() void { + inline for ([_]type{ f16, f32, f64, f128 }) |T| { + const ST = std.meta.Int(.unsigned, @typeInfo(T).Float.bits); + var as_fp_val = -@as(T, 0.0); + var as_uint_val = @bitCast(ST, as_fp_val); + // Ensure the sign bit is set. + expect(as_uint_val >> (@typeInfo(T).Float.bits - 1) == 1); + } + } + }; + + S.doTheTest(); + comptime S.doTheTest(); +} From 7a4dad7e87bd5561bbe62f5d38fda1b968f9d529 Mon Sep 17 00:00:00 2001 From: LemonBoy Date: Thu, 15 Apr 2021 18:17:31 +0200 Subject: [PATCH 4/7] stage1: More precise serialization of f16 values Taking a detour trough a f64 is dangerous as the softfloat library doesn't like converting sNaN values. The error went unnoticed as an exception is raised by the library but the stage1 compiler doesn't give a damn. --- src/stage1/codegen.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/stage1/codegen.cpp b/src/stage1/codegen.cpp index 6d219c517d..3bb1a7e022 100644 --- a/src/stage1/codegen.cpp +++ b/src/stage1/codegen.cpp @@ -7436,7 +7436,10 @@ static LLVMValueRef gen_const_val(CodeGen *g, ZigValue *const_val, const char *n case ZigTypeIdFloat: switch (type_entry->data.floating.bit_count) { case 16: - return LLVMConstReal(get_llvm_type(g, type_entry), zig_f16_to_double(const_val->data.x_f16)); + { + LLVMValueRef as_int = LLVMConstInt(LLVMInt16Type(), const_val->data.x_f16.v, false); + return LLVMConstBitCast(as_int, get_llvm_type(g, type_entry)); + } case 32: return LLVMConstReal(get_llvm_type(g, type_entry), const_val->data.x_f32); case 64: From b29677dd12b5a19618295757a6044c0b0d741dbb Mon Sep 17 00:00:00 2001 From: LemonBoy Date: Thu, 15 Apr 2021 18:20:50 +0200 Subject: [PATCH 5/7] compiler-rt: Implement __extendhftf2 --- lib/std/special/compiler_rt/extendXfYf2.zig | 4 ++ .../special/compiler_rt/extendXfYf2_test.zig | 49 ++++++++++++++++++- 2 files changed, 52 insertions(+), 1 deletion(-) diff --git a/lib/std/special/compiler_rt/extendXfYf2.zig b/lib/std/special/compiler_rt/extendXfYf2.zig index c5b93fa51e..59be8441fa 100644 --- a/lib/std/special/compiler_rt/extendXfYf2.zig +++ b/lib/std/special/compiler_rt/extendXfYf2.zig @@ -23,6 +23,10 @@ pub fn __extendhfsf2(a: u16) callconv(.C) f32 { return @call(.{ .modifier = .always_inline }, extendXfYf2, .{ f32, f16, a }); } +pub fn __extendhftf2(a: u16) callconv(.C) f128 { + return @call(.{ .modifier = .always_inline }, extendXfYf2, .{ f128, f16, a }); +} + pub fn __aeabi_h2f(arg: u16) callconv(.AAPCS) f32 { @setRuntimeSafety(false); return @call(.{ .modifier = .always_inline }, __extendhfsf2, .{arg}); diff --git a/lib/std/special/compiler_rt/extendXfYf2_test.zig b/lib/std/special/compiler_rt/extendXfYf2_test.zig index 6a3f69d8e9..d97a9c2502 100644 --- a/lib/std/special/compiler_rt/extendXfYf2_test.zig +++ b/lib/std/special/compiler_rt/extendXfYf2_test.zig @@ -4,9 +4,10 @@ // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. const builtin = @import("builtin"); -const __extenddftf2 = @import("extendXfYf2.zig").__extenddftf2; const __extendhfsf2 = @import("extendXfYf2.zig").__extendhfsf2; +const __extendhftf2 = @import("extendXfYf2.zig").__extendhftf2; const __extendsftf2 = @import("extendXfYf2.zig").__extendsftf2; +const __extenddftf2 = @import("extendXfYf2.zig").__extenddftf2; fn test__extenddftf2(a: f64, expectedHi: u64, expectedLo: u64) void { const x = __extenddftf2(a); @@ -161,3 +162,49 @@ fn makeNaN32(rand: u32) f32 { fn makeInf32() f32 { return @bitCast(f32, @as(u32, 0x7f800000)); } + +fn test__extendhftf2(a: f16, expectedHi: u64, expectedLo: u64) void { + const x = __extendhftf2(@bitCast(u16, a)); + + const rep = @bitCast(u128, x); + const hi = @intCast(u64, rep >> 64); + const lo = @truncate(u64, rep); + + if (hi == expectedHi and lo == expectedLo) + return; + + // test other possible NaN representation(signal NaN) + if (expectedHi == 0x7fff800000000000 and expectedLo == 0x0) { + if ((hi & 0x7fff000000000000) == 0x7fff000000000000 and + ((hi & 0xffffffffffff) > 0 or lo > 0)) + { + return; + } + } + + @panic("__extendhftf2 test failure"); +} + +test "extendhftf2" { + // qNaN + test__extendhftf2(@bitCast(f16, @as(u16, 0x7e00)), 0x7fff800000000000, 0x0); + // NaN + test__extendhftf2(@bitCast(f16, @as(u16, 0x7d00)), 0x7fff400000000000, 0x0); + // inf + test__extendhftf2(@bitCast(f16, @as(u16, 0x7c00)), 0x7fff000000000000, 0x0); + test__extendhftf2(-@bitCast(f16, @as(u16, 0x7c00)), 0xffff000000000000, 0x0); + // zero + test__extendhftf2(@bitCast(f16, @as(u16, 0x0)), 0x0, 0x0); + test__extendhftf2(@bitCast(f16, @as(u16, 0x8000)), 0x8000000000000000, 0x0); + // denormal + test__extendhftf2(@bitCast(f16, @as(u16, 0x0010)), 0x3feb000000000000, 0x0000000000000000); + test__extendhftf2(@bitCast(f16, @as(u16, 0x0001)), 0x3fe7000000000000, 0x0000000000000000); + test__extendhftf2(@bitCast(f16, @as(u16, 0x8001)), 0xbfe7000000000000, 0x0000000000000000); + + // pi + test__extendhftf2(@bitCast(f16, @as(u16, 0x4248)), 0x4000920000000000, 0x0000000000000000); + test__extendhftf2(@bitCast(f16, @as(u16, 0xc248)), 0xc000920000000000, 0x0000000000000000); + + test__extendhftf2(@bitCast(f16, @as(u16, 0x508c)), 0x4004230000000000, 0x0); + test__extendhftf2(@bitCast(f16, @as(u16, 0x1bb7)), 0x3ff6edc000000000, 0x0); +} From 5bc1dc59e693287e1ffcd14023048b2abc7e1764 Mon Sep 17 00:00:00 2001 From: LemonBoy Date: Thu, 15 Apr 2021 18:21:15 +0200 Subject: [PATCH 6/7] compiler-rt: Implement __trunctfhf2 --- lib/std/special/compiler_rt/truncXfYf2.zig | 4 ++ .../special/compiler_rt/truncXfYf2_test.zig | 56 +++++++++++++++++++ 2 files changed, 60 insertions(+) diff --git a/lib/std/special/compiler_rt/truncXfYf2.zig b/lib/std/special/compiler_rt/truncXfYf2.zig index cae5644962..e85aa363db 100644 --- a/lib/std/special/compiler_rt/truncXfYf2.zig +++ b/lib/std/special/compiler_rt/truncXfYf2.zig @@ -13,6 +13,10 @@ pub fn __truncdfhf2(a: f64) callconv(.C) u16 { return @bitCast(u16, @call(.{ .modifier = .always_inline }, truncXfYf2, .{ f16, f64, a })); } +pub fn __trunctfhf2(a: f128) callconv(.C) u16 { + return @bitCast(u16, @call(.{ .modifier = .always_inline }, truncXfYf2, .{ f16, f128, a })); +} + pub fn __trunctfsf2(a: f128) callconv(.C) f32 { return @call(.{ .modifier = .always_inline }, truncXfYf2, .{ f32, f128, a }); } diff --git a/lib/std/special/compiler_rt/truncXfYf2_test.zig b/lib/std/special/compiler_rt/truncXfYf2_test.zig index 6426614b07..4fae5d1fc0 100644 --- a/lib/std/special/compiler_rt/truncXfYf2_test.zig +++ b/lib/std/special/compiler_rt/truncXfYf2_test.zig @@ -242,3 +242,59 @@ test "truncdfsf2" { // huge number becomes inf test__truncdfsf2(340282366920938463463374607431768211456.0, 0x7f800000); } + +const __trunctfhf2 = @import("truncXfYf2.zig").__trunctfhf2; + +fn test__trunctfhf2(a: f128, expected: u16) void { + const x = __trunctfhf2(a); + + const rep = @bitCast(u16, x); + if (rep == expected) { + return; + } + + @import("std").debug.warn("got 0x{x} wanted 0x{x}\n", .{ rep, expected }); + + @panic("__trunctfhf2 test failure"); +} + +test "trunctfhf2" { + // qNaN + test__trunctfhf2(@bitCast(f128, @as(u128, 0x7fff8000000000000000000000000000)), 0x7e00); + // NaN + test__trunctfhf2(@bitCast(f128, @as(u128, 0x7fff0000000000000000000000000001)), 0x7e00); + // inf + test__trunctfhf2(@bitCast(f128, @as(u128, 0x7fff0000000000000000000000000000)), 0x7c00); + test__trunctfhf2(-@bitCast(f128, @as(u128, 0x7fff0000000000000000000000000000)), 0xfc00); + // zero + test__trunctfhf2(0.0, 0x0); + test__trunctfhf2(-0.0, 0x8000); + + test__trunctfhf2(3.1415926535, 0x4248); + test__trunctfhf2(-3.1415926535, 0xc248); + test__trunctfhf2(0x1.987124876876324p+100, 0x7c00); + test__trunctfhf2(0x1.987124876876324p+12, 0x6e62); + test__trunctfhf2(0x1.0p+0, 0x3c00); + test__trunctfhf2(0x1.0p-14, 0x0400); + // denormal + test__trunctfhf2(0x1.0p-20, 0x0010); + test__trunctfhf2(0x1.0p-24, 0x0001); + test__trunctfhf2(-0x1.0p-24, 0x8001); + test__trunctfhf2(0x1.5p-25, 0x0001); + // and back to zero + test__trunctfhf2(0x1.0p-25, 0x0000); + test__trunctfhf2(-0x1.0p-25, 0x8000); + // max (precise) + test__trunctfhf2(65504.0, 0x7bff); + // max (rounded) + test__trunctfhf2(65519.0, 0x7bff); + // max (to +inf) + test__trunctfhf2(65520.0, 0x7c00); + test__trunctfhf2(65536.0, 0x7c00); + test__trunctfhf2(-65520.0, 0xfc00); + + test__trunctfhf2(0x1.23a2abb4a2ddee355f36789abcdep+5, 0x508f); + test__trunctfhf2(0x1.e3d3c45bd3abfd98b76a54cc321fp-9, 0x1b8f); + test__trunctfhf2(0x1.234eebb5faa678f4488693abcdefp+453, 0x7c00); + test__trunctfhf2(0x1.edcba9bb8c76a5a43dd21f334634p-43, 0x0); +} From bd4421befee2157018dad88b31b684f9a5af73c2 Mon Sep 17 00:00:00 2001 From: LemonBoy Date: Thu, 15 Apr 2021 21:52:08 +0200 Subject: [PATCH 7/7] compiler-rt: Don't pass f16 around as arguments Fixes some failures on AArch64. f16 was a mistake. --- .../special/compiler_rt/extendXfYf2_test.zig | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/lib/std/special/compiler_rt/extendXfYf2_test.zig b/lib/std/special/compiler_rt/extendXfYf2_test.zig index d97a9c2502..f05d33eac3 100644 --- a/lib/std/special/compiler_rt/extendXfYf2_test.zig +++ b/lib/std/special/compiler_rt/extendXfYf2_test.zig @@ -163,8 +163,8 @@ fn makeInf32() f32 { return @bitCast(f32, @as(u32, 0x7f800000)); } -fn test__extendhftf2(a: f16, expectedHi: u64, expectedLo: u64) void { - const x = __extendhftf2(@bitCast(u16, a)); +fn test__extendhftf2(a: u16, expectedHi: u64, expectedLo: u64) void { + const x = __extendhftf2(a); const rep = @bitCast(u128, x); const hi = @intCast(u64, rep >> 64); @@ -187,24 +187,24 @@ fn test__extendhftf2(a: f16, expectedHi: u64, expectedLo: u64) void { test "extendhftf2" { // qNaN - test__extendhftf2(@bitCast(f16, @as(u16, 0x7e00)), 0x7fff800000000000, 0x0); + test__extendhftf2(0x7e00, 0x7fff800000000000, 0x0); // NaN - test__extendhftf2(@bitCast(f16, @as(u16, 0x7d00)), 0x7fff400000000000, 0x0); + test__extendhftf2(0x7d00, 0x7fff400000000000, 0x0); // inf - test__extendhftf2(@bitCast(f16, @as(u16, 0x7c00)), 0x7fff000000000000, 0x0); - test__extendhftf2(-@bitCast(f16, @as(u16, 0x7c00)), 0xffff000000000000, 0x0); + test__extendhftf2(0x7c00, 0x7fff000000000000, 0x0); + test__extendhftf2(0xfc00, 0xffff000000000000, 0x0); // zero - test__extendhftf2(@bitCast(f16, @as(u16, 0x0)), 0x0, 0x0); - test__extendhftf2(@bitCast(f16, @as(u16, 0x8000)), 0x8000000000000000, 0x0); + test__extendhftf2(0x0000, 0x0000000000000000, 0x0); + test__extendhftf2(0x8000, 0x8000000000000000, 0x0); // denormal - test__extendhftf2(@bitCast(f16, @as(u16, 0x0010)), 0x3feb000000000000, 0x0000000000000000); - test__extendhftf2(@bitCast(f16, @as(u16, 0x0001)), 0x3fe7000000000000, 0x0000000000000000); - test__extendhftf2(@bitCast(f16, @as(u16, 0x8001)), 0xbfe7000000000000, 0x0000000000000000); + test__extendhftf2(0x0010, 0x3feb000000000000, 0x0); + test__extendhftf2(0x0001, 0x3fe7000000000000, 0x0); + test__extendhftf2(0x8001, 0xbfe7000000000000, 0x0); // pi - test__extendhftf2(@bitCast(f16, @as(u16, 0x4248)), 0x4000920000000000, 0x0000000000000000); - test__extendhftf2(@bitCast(f16, @as(u16, 0xc248)), 0xc000920000000000, 0x0000000000000000); + test__extendhftf2(0x4248, 0x4000920000000000, 0x0); + test__extendhftf2(0xc248, 0xc000920000000000, 0x0); - test__extendhftf2(@bitCast(f16, @as(u16, 0x508c)), 0x4004230000000000, 0x0); - test__extendhftf2(@bitCast(f16, @as(u16, 0x1bb7)), 0x3ff6edc000000000, 0x0); + test__extendhftf2(0x508c, 0x4004230000000000, 0x0); + test__extendhftf2(0x1bb7, 0x3ff6edc000000000, 0x0); }