From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from simark.ca by simark.ca with LMTP id r3ljEgQk4mqYlD4AWB0awg (envelope-from ) for ; Wed, 28 Oct 2026 12:34:12 -0400 Received: by simark.ca (Postfix, from userid 112) id 36041378002; Wed, 28 Oct 2026 12:34:12 -0400 (EDT) X-Spam-Checker-Version: SpamAssassin 4.0.1 (2024-03-25) on simark.ca X-Spam-Level: X-Spam-Status: No, score=-5.3 required=5.0 tests=ARC_SIGNED,ARC_VALID,BAYES_00, MAILING_LIST_MULTI,RCVD_IN_DNSWL_MED autolearn=ham autolearn_force=no version=4.0.1 Received: from vm01.sourceware.org (vm01.sourceware.org [IPv6:2620:52:6:3111::32]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange x25519 server-signature ECDSA (prime256v1) server-digest SHA256) (No client certificate requested) by simark.ca (Postfix) with ESMTPS id 30C501E007 for ; Wed, 28 Oct 2026 12:34:10 -0400 (EDT) Received: from vm01.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id CD22E4BA2E23 for ; Wed, 28 Oct 2026 16:34:08 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org CD22E4BA2E23 Received: from zmcc-4-mx.zmailcloud.com (zmcc-4-mx.zmailcloud.com [34.203.170.87]) by sourceware.org (Postfix) with ESMTPS id 536614BA23D7 for ; Wed, 28 Oct 2026 16:33:24 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 536614BA23D7 Authentication-Results: sourceware.org; dmarc=none (p=none dis=none) header.from=symas.com Authentication-Results: sourceware.org; spf=pass smtp.mailfrom=symas.com ARC-Filter: OpenARC Filter v1.0.0 sourceware.org 536614BA23D7 Authentication-Results: sourceware.org; arc=none smtp.remote-ip=34.203.170.87 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1793205204; cv=none; b=ES9XN140zcmokOe9YnFwZrZpcG782hAxFP6NfFaINiLc9s637VnccrN1d7OH2CHI9jH7UX4Z6EQLt+uavjwATAYi1VqWbwRC8GjPtpBQlAxKN1Dv8zgB4R4/n9ixIXH703ptsa1AnWgTcCJ/Zq8WCU91YRwAXa9guFJ7mBDiS94= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1793205204; c=relaxed/simple; bh=6LqufselQbrJJQfSoktYmuorlPEG8obsdIlPou9y+3w=; h=From:To:Subject:Date:Message-ID:MIME-Version; b=mqlHqYev8SKvzPCRI3nRifRv+HzxbMQf24+ilNy1wTSCumXR0HTEx2Bqh6fBE8G5FqVtR/OT61WECYeiadmrobjFu7efD24gdYfeAaFVV+VAb6L5Voh7E7NwTSXnNVkfnLpdARGCp8XRIOZrtg/MjIdXiLWlgtn5GXWyivrykpM= ARC-Authentication-Results: i=1; sourceware.org DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 536614BA23D7 Received: from zmcc-3.zmailcloud.com (ec2-3-15-255-223.us-east-2.compute.amazonaws.com [3.15.255.223]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by zmcc-4-mx.zmailcloud.com (Postfix) with ESMTPS id F0B6D7801C16 for ; Wed, 28 Oct 2026 11:33:23 -0500 (CDT) Received: from zmcc-3.zmailcloud.com (localhost [127.0.0.1]) by zmcc-3-mta-1.zmailcloud.com (Postfix) with ESMTPS id CF6A61043EBE for ; Wed, 28 Oct 2026 11:33:23 -0500 (CDT) Received: from localhost (localhost [127.0.0.1]) by zmcc-3-mta-1.zmailcloud.com (Postfix) with ESMTP id B83241043EBF for ; Wed, 28 Oct 2026 11:33:23 -0500 (CDT) Received: from zmcc-3.zmailcloud.com ([127.0.0.1]) by localhost (zmcc-3-mta-1.zmailcloud.com [127.0.0.1]) (amavis, port 10026) with ESMTP id ER9KijQrlRdG for ; Wed, 28 Oct 2026 11:33:23 -0500 (CDT) Received: from zmcc-3-mailbox-1.zmailcloud.com (zmcc-3-mailbox-1.zmailcloud.com [172.31.18.168]) by zmcc-3-mta-1.zmailcloud.com (Postfix) with ESMTP id 9B2D01043EBE for ; Wed, 28 Oct 2026 11:33:23 -0500 (CDT) From: Robert Dubner To: Subject: [PATCH] gdb: Fix signed-zero and subnormal conversions in target floating-point support Thread-Topic: [PATCH] gdb: Fix signed-zero and subnormal conversions in target floating-point support Date: Wed, 28 Oct 2026 11:33:23 -0500 (CDT) Message-ID: <1b1401dd66fa$0dc45250$294cf6f0$@symas.com> MIME-Version: 1.0 Content-Type: text/plain; charset="US-ASCII" Content-Transfer-Encoding: 7bit X-Mailer: Microsoft Outlook 16.0 X-Mailer: Zimbra 10.1.21_GA_4899 (Zimbra-ZCO/1951 (10.0.26200 en-US) P6c38 T3694 R27099) Thread-Index: Ad1m+b6/O1pvWpaITridwpb1MbHQKQ== Content-Language: en-us X-Zimbra-Message-Verification: hash=SHA256; guid=MTczMGI2MTE1M2I4NDMwNjQzY2ZmMGI0ZDNhOGU0OTczZmRmZmQ1MDE2MzZmNDBhNGE5MGM4MGFkYTExMzk5Yg== X-BeenThere: gdb-patches@sourceware.org X-Mailman-Version: 2.1.30 Precedence: list List-Id: Gdb-patches mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: gdb-patches-bounces~public-inbox=simark.ca@sourceware.org >From 461b097d2319f771a5ffb28273782115b3017e13 Mon Sep 17 00:00:00 2001 From: Robert Dubner Date: Wed, 28 Oct 2026 12:24:55 -0400 Subject: [PATCH] gdb: Fix signed-zero and subnormal conversions in target floating-point support Preserve IEEE negative zero when encoding MPFR values and correctly encode subnormal significands and exponents. Carry MPFR rounding information through conversions and arithmetic to avoid double rounding when producing subnormal results. Limit partial-byte writes in put_field to the requested field width, preventing exponent writes from clearing the sign bit of binary16 values. Add generic selftests covering signed zero, subnormal round trips and rounding boundaries across floating-point formats and byte orders. Extend gdb.base/float128 with conversion and arithmetic regressions. Validation: all five generic selftests passed. Full DejaGNU comparison identified no new regressions: 471 common baseline failures remained, along with two previously observed intermittent process-exit failures. --- gdb/Makefile.in | 1 + gdb/target-float.c | 128 ++++++---- gdb/testsuite/gdb.base/float128.c | 9 + gdb/testsuite/gdb.base/float128.exp | 26 +++ gdb/unittests/target-float-selftests.c | 309 +++++++++++++++++++++++++ 5 files changed, 425 insertions(+), 48 deletions(-) create mode 100644 gdb/unittests/target-float-selftests.c diff --git a/gdb/Makefile.in b/gdb/Makefile.in index b0a1e18ad44..b7adbdd47bb 100644 --- a/gdb/Makefile.in +++ b/gdb/Makefile.in @@ -491,6 +491,7 @@ SELFTESTS_SRCS = \ unittests/scoped_restore-selftests.c \ unittests/search-memory-selftests.c \ unittests/style-selftests.c \ + unittests/target-float-selftests.c \ unittests/tracepoint-selftests.c \ unittests/tui-selftests.c \ unittests/ui-file-selftests.c \ diff --git a/gdb/target-float.c b/gdb/target-float.c index 31b5ac642a7..47aed2661ed 100644 --- a/gdb/target-float.c +++ b/gdb/target-float.c @@ -21,6 +21,7 @@ #include "floatformat.h" #include "target-float.h" #include "gdbarch.h" +#include "gdbsupport/scope-exit.h" /* Target floating-point operations. @@ -229,7 +230,7 @@ get_field (const bfd_byte *data, enum floatformat_byteorders order, return result; } -/* Set a field which starts at START and is LEN bytes long. DATA and +/* Set a field which starts at START and is LEN bits long. DATA and TOTAL_LEN are the thing we are extracting it from, in byteorder ORDER. */ static void put_field (unsigned char *data, enum floatformat_byteorders order, @@ -260,11 +261,12 @@ put_field (unsigned char *data, enum floatformat_byteorders order, } if (cur_bitshift > -FLOATFORMAT_CHAR_BIT) { - *(data + cur_byte) &= - ~(((1 << ((start + len) % FLOATFORMAT_CHAR_BIT)) - 1) - << (-cur_bitshift)); - *(data + cur_byte) |= - (stuff_to_put & ((1 << FLOATFORMAT_CHAR_BIT) - 1)) << (-cur_bitshift); + /* The whole field may fit in this byte. Do not clear or set + neighboring bits outside LEN (e.g. the sign of an IEEE half). */ + unsigned long mask + = (1UL << std::min (len, (start + len) % FLOATFORMAT_CHAR_BIT)) - 1; + *(data + cur_byte) &= ~(mask << (-cur_bitshift)); + *(data + cur_byte) |= (stuff_to_put & mask) << (-cur_bitshift); } cur_bitshift += FLOATFORMAT_CHAR_BIT; if (order == floatformat_little) @@ -1217,9 +1219,9 @@ class mpfr_float_ops : public target_float_ops const gdb_byte *from, gdb_mpfr &to) const; void to_target (const struct type *type, - const gdb_mpfr &from, gdb_byte *to) const; + const gdb_mpfr &from, gdb_byte *to, int inexact = 0) const; void to_target (const struct floatformat *fmt, - const gdb_mpfr &from, gdb_byte *to) const; + const gdb_mpfr &from, gdb_byte *to, int inexact = 0) const; }; @@ -1342,12 +1344,14 @@ mpfr_float_ops::from_target (const struct type *type, void mpfr_float_ops::to_target (const struct floatformat *fmt, - const gdb_mpfr &from, gdb_byte *orig_to) const + const gdb_mpfr &from, gdb_byte *orig_to, + int inexact) const { unsigned char *to = orig_to; mpfr_exp_t exponent; unsigned int mant_bits, mant_off; int mant_bits_left; + bool subnormal; enum floatformat_byteorders order = fmt->byteorder; unsigned char newto[FLOATFORMAT_LARGEST_BYTES]; @@ -1379,9 +1383,6 @@ mpfr_float_ops::to_target (const struct floatformat *fmt, gdb_mpfr tmp (from); - if (mpfr_zero_p (from.val)) - goto finalize_byteorder; /* Result is zero */ - mpfr_set (tmp.val, from.val, MPFR_RNDN); if (mpfr_nan_p (tmp.val)) /* Result is NaN */ @@ -1396,12 +1397,43 @@ mpfr_float_ops::to_target (const struct floatformat *fmt, } /* If negative, set the sign bit. */ - if (mpfr_sgn (tmp.val) < 0) + if (mpfr_signbit (tmp.val)) { - put_field (to, order, fmt->totalsize, fmt->sign_start, 1, 1); + /* Keep zero encodings unchanged for non-IEEE formats (e.g. VAX) + which do not have signed zero. */ + if (!mpfr_zero_p (tmp.val) || fmt->exp_nan != 0) + put_field (to, order, fmt->totalsize, fmt->sign_start, 1, 1); mpfr_neg (tmp.val, tmp.val, MPFR_RNDN); + inexact = -inexact; + } + + if (mpfr_zero_p (tmp.val)) + goto finalize_byteorder; + + /* Subnormal values have fewer significant bits than normal values. Use + the operation's ternary result to avoid double rounding at halfway + cases. Restrict the exponent range only while rounding; the encoding + below needs intermediates outside the target's range (e.g. binary16). + Formats without an infinity/NaN exponent do not support subnormals. */ + if (!mpfr_inf_p (tmp.val) && fmt->exp_nan != 0 + && mpfr_get_exp (tmp.val) + fmt->exp_bias <= 1) + { + mpfr_exp_t saved_emin = mpfr_get_emin (); + mpfr_exp_t saved_emax = mpfr_get_emax (); + SCOPE_EXIT + { + mpfr_set_emin (saved_emin); + mpfr_set_emax (saved_emax); + }; + mpfr_set_emin (3 - fmt->exp_bias - floatformat_precision (fmt)); + mpfr_set_emax (fmt->exp_nan - fmt->exp_bias); + inexact = mpfr_check_range (tmp.val, inexact, MPFR_RNDN); + mpfr_subnormalize (tmp.val, inexact, MPFR_RNDN); } + if (mpfr_zero_p (tmp.val)) + goto finalize_byteorder; + if (mpfr_inf_p (tmp.val)) /* Result is Infinity. */ { /* Infinity exponent is same as NaN's. */ @@ -1415,16 +1447,18 @@ mpfr_float_ops::to_target (const struct floatformat *fmt, mpfr_frexp (&exponent, tmp.val, tmp.val, MPFR_RNDN); - if (exponent + fmt->exp_bias <= 0) + subnormal = fmt->exp_nan != 0 && exponent + fmt->exp_bias <= 1; + if (subnormal) { - /* The value is too small to be expressed in the destination - type (not enough bits in the exponent. Treat as 0. */ - put_field (to, order, fmt->totalsize, fmt->exp_start, - fmt->exp_len, 0); - put_field (to, order, fmt->totalsize, fmt->man_start, - fmt->man_len, 0); - goto finalize_byteorder; + /* Align to the minimum normal exponent, leaving the leading zero + explicit even for formats with an implicit integer bit. */ + mpfr_mul_2si (tmp.val, tmp.val, + exponent + fmt->exp_bias + - (fmt->intbit == floatformat_intbit_no ? 1 : 2), + MPFR_RNDN); } + else if (exponent + fmt->exp_bias <= 0) + goto finalize_byteorder; if (exponent + fmt->exp_bias >= (1 << fmt->exp_len)) { @@ -1438,7 +1472,7 @@ mpfr_float_ops::to_target (const struct floatformat *fmt, } put_field (to, order, fmt->totalsize, fmt->exp_start, fmt->exp_len, - exponent + fmt->exp_bias - 1); + subnormal ? 0 : exponent + fmt->exp_bias - 1); mant_bits_left = fmt->man_len; mant_off = fmt->man_start; @@ -1452,11 +1486,8 @@ mpfr_float_ops::to_target (const struct floatformat *fmt, mant_long = mpfr_get_ui (tmp.val, MPFR_RNDZ) & 0xffffffffL; mpfr_sub_ui (tmp.val, tmp.val, mant_long, MPFR_RNDZ); - /* If the integer bit is implicit, then we need to discard it. - If we are discarding a zero, we should be (but are not) creating - a denormalized number which means adjusting the exponent - (I think). */ - if (mant_bits_left == fmt->man_len + /* Only normal values have an implicit leading one to discard. */ + if (!subnormal && mant_bits_left == fmt->man_len && fmt->intbit == floatformat_intbit_no) { mant_long <<= 1; @@ -1493,12 +1524,12 @@ mpfr_float_ops::to_target (const struct floatformat *fmt, void mpfr_float_ops::to_target (const struct type *type, - const gdb_mpfr &from, gdb_byte *to) const + const gdb_mpfr &from, gdb_byte *to, int inexact) const { /* Ensure possible padding bytes in the target buffer are zeroed out. */ memset (to, 0, type->length ()); - to_target (floatformat_from_type (type), from, to); + to_target (floatformat_from_type (type), from, to, inexact); } /* Convert the byte-stream ADDR, interpreted as floating-point type TYPE, @@ -1556,13 +1587,13 @@ mpfr_float_ops::from_string (gdb_byte *addr, gdb_mpfr tmp (type); char *endptr; - mpfr_strtofr (tmp.val, in.c_str (), &endptr, 0, MPFR_RNDN); + int inexact = mpfr_strtofr (tmp.val, in.c_str (), &endptr, 0, MPFR_RNDN); /* We only accept the whole string. */ if (*endptr) return false; - to_target (type, tmp, addr); + to_target (type, tmp, addr, inexact); return true; } @@ -1585,8 +1616,8 @@ mpfr_float_ops::from_longest (gdb_byte *addr, LONGEST val) const { gdb_mpfr tmp (type); - mpfr_set_sj (tmp.val, val, MPFR_RNDN); - to_target (type, tmp, addr); + int inexact = mpfr_set_sj (tmp.val, val, MPFR_RNDN); + to_target (type, tmp, addr, inexact); } /* Convert unsigned integer VAL to a target floating-number of type TYPE @@ -1597,8 +1628,8 @@ mpfr_float_ops::from_ulongest (gdb_byte *addr, ULONGEST val) const { gdb_mpfr tmp (type); - mpfr_set_uj (tmp.val, val, MPFR_RNDN); - to_target (type, tmp, addr); + int inexact = mpfr_set_uj (tmp.val, val, MPFR_RNDN); + to_target (type, tmp, addr, inexact); } /* Convert the byte-stream ADDR, interpreted as floating-point type TYPE, @@ -1620,8 +1651,8 @@ mpfr_float_ops::from_host_double (gdb_byte *addr, double val) const { gdb_mpfr tmp (type); - mpfr_set_d (tmp.val, val, MPFR_RNDN); - to_target (type, tmp, addr); + int inexact = mpfr_set_d (tmp.val, val, MPFR_RNDN); + to_target (type, tmp, addr, inexact); } /* Convert a floating-point number of type FROM_TYPE from the target @@ -1635,8 +1666,8 @@ mpfr_float_ops::convert (const gdb_byte *from, { gdb_mpfr from_tmp (from_type), to_tmp (to_type); from_target (from_type, from, from_tmp); - mpfr_set (to_tmp.val, from_tmp.val, MPFR_RNDN); - to_target (to_type, to_tmp, to); + int inexact = mpfr_set (to_tmp.val, from_tmp.val, MPFR_RNDN); + to_target (to_type, to_tmp, to, inexact); } /* Perform the binary operation indicated by OPCODE, using as operands the @@ -1654,34 +1685,35 @@ mpfr_float_ops::binop (enum exp_opcode op, from_target (type_x, x, x_tmp); from_target (type_y, y, y_tmp); + int inexact; switch (op) { case BINOP_ADD: - mpfr_add (tmp.val, x_tmp.val, y_tmp.val, MPFR_RNDN); + inexact = mpfr_add (tmp.val, x_tmp.val, y_tmp.val, MPFR_RNDN); break; case BINOP_SUB: - mpfr_sub (tmp.val, x_tmp.val, y_tmp.val, MPFR_RNDN); + inexact = mpfr_sub (tmp.val, x_tmp.val, y_tmp.val, MPFR_RNDN); break; case BINOP_MUL: - mpfr_mul (tmp.val, x_tmp.val, y_tmp.val, MPFR_RNDN); + inexact = mpfr_mul (tmp.val, x_tmp.val, y_tmp.val, MPFR_RNDN); break; case BINOP_DIV: - mpfr_div (tmp.val, x_tmp.val, y_tmp.val, MPFR_RNDN); + inexact = mpfr_div (tmp.val, x_tmp.val, y_tmp.val, MPFR_RNDN); break; case BINOP_EXP: - mpfr_pow (tmp.val, x_tmp.val, y_tmp.val, MPFR_RNDN); + inexact = mpfr_pow (tmp.val, x_tmp.val, y_tmp.val, MPFR_RNDN); break; case BINOP_MIN: - mpfr_min (tmp.val, x_tmp.val, y_tmp.val, MPFR_RNDN); + inexact = mpfr_min (tmp.val, x_tmp.val, y_tmp.val, MPFR_RNDN); break; case BINOP_MAX: - mpfr_max (tmp.val, x_tmp.val, y_tmp.val, MPFR_RNDN); + inexact = mpfr_max (tmp.val, x_tmp.val, y_tmp.val, MPFR_RNDN); break; default: @@ -1689,7 +1721,7 @@ mpfr_float_ops::binop (enum exp_opcode op, break; } - to_target (type_res, tmp, res); + to_target (type_res, tmp, res, inexact); } /* Compare the two target byte streams X and Y, interpreted as floating-point diff --git a/gdb/testsuite/gdb.base/float128.c b/gdb/testsuite/gdb.base/float128.c index 029477e9e5a..895f339ef89 100644 --- a/gdb/testsuite/gdb.base/float128.c +++ b/gdb/testsuite/gdb.base/float128.c @@ -25,6 +25,15 @@ __float128 f128; // __float128 data type by definition is encoded as IEEE-128. __float128 large128 = 1.18973149535723176508575932662800702e+4932q; +/* Values supplied by the compiler exercise conversions independently of + GDB's parsing of floating-point constants. */ +__float128 least128 = 0x1p-16494q; +__float128 largest_subnormal128 = 0x0.ffffffffffffffffffffffffffffp-16382q; +__float128 normal128 = 0x1p-16382q; +float least32 = 0x1p-149f; +double least64 = 0x1p-1074; +double negative_zero = -0.0; + int main() { ld = 1.375l; diff --git a/gdb/testsuite/gdb.base/float128.exp b/gdb/testsuite/gdb.base/float128.exp index 37aa5136416..929a4c917fd 100644 --- a/gdb/testsuite/gdb.base/float128.exp +++ b/gdb/testsuite/gdb.base/float128.exp @@ -75,3 +75,29 @@ gdb_test "print f128" ".* = 20\\.375.*" "the value of f128 is changed to 20.375" # Test that we can correctly handle the largest IEEE-128 value gdb_test "print large128" ".* = 1\\.18973149535723176508575932662800702e\\+4932" + +# These operations use GDB's target floating-point conversion, rather than +# calling arithmetic helpers in the inferior. +with_test_prefix "signed zero" { + gdb_test "print f128 = negative_zero" " = -0" "widen negative zero" + gdb_test "print (double) f128" " = -0" "narrow negative zero" +} + +with_test_prefix "subnormal" { + gdb_test_no_output "set variable f128 = least128 * 1" "multiply least value" + gdb_test "print f128 == least128" " = 1" "preserve least value" + gdb_test "print normal128 - least128 == largest_subnormal128" " = 1" \ + "normal to subnormal boundary" + gdb_test "print largest_subnormal128 + least128 == normal128" " = 1" \ + "subnormal to normal boundary" + gdb_test_no_output "set variable f128 = least128 / 2" "positive underflow" + gdb_test "print f128" " = 0" "positive underflow zero" + gdb_test_no_output "set variable f128 = -least128 / 2" "negative underflow" + gdb_test "print f128" " = -0" "negative underflow zero" + foreach bits {32 64} { + gdb_test_no_output "set variable f128 = least$bits" "widen binary$bits" + set destination [expr {$bits == 32 ? "float" : "double"}] + gdb_test "print ($destination) f128 == least$bits" " = 1" \ + "roundtrip binary$bits" + } +} diff --git a/gdb/unittests/target-float-selftests.c b/gdb/unittests/target-float-selftests.c new file mode 100644 index 00000000000..9fac2da89a4 --- /dev/null +++ b/gdb/unittests/target-float-selftests.c @@ -0,0 +1,309 @@ +/* Self tests for target floating-point conversions + + Copyright (C) 2026 Free Software Foundation, Inc. + + This file is part of GDB. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#include "gdbsupport/selftest.h" +#include "gdbtypes.h" +#include "gdbarch.h" +#include "inferior.h" +#include "floatformat.h" +#include "target-float.h" +#include "gmp-utils.h" +#include + +namespace selftests { +namespace target_float { + +/* Distinct format descriptors force the generic MPFR backend, even on hosts + whose float, double or long double already uses one of these IEEE formats. + Static lifetime also matches that of the architecture-owned test types. */ +static const floatformat formats[][2] = { + {floatformat_ieee_single_big, floatformat_ieee_single_little}, + {floatformat_ieee_double_big, floatformat_ieee_double_little}, + {floatformat_ieee_quad_big, floatformat_ieee_quad_little}, + {floatformat_ieee_half_big, floatformat_ieee_half_little} +}; + +static type * +make_type (int index, bfd_endian order) +{ + const floatformat *pair[] = {&formats[index][0], &formats[index][1]}; + type_allocator alloc (current_inferior ()->arch ()); + return init_float_type (alloc, -1, "selftest_float", pair, order); +} + +static std::vector +encode (const gdb_mpz &bits, type *ftype, bfd_endian order) +{ + std::vector result (ftype->length ()); + bits.write (result, order, true); + return result; +} + +static std::string +dyadic_spelling (const gdb_mpz &coefficient, int exponent) +{ + return coefficient.hex () + "p" + std::to_string (exponent); +} + +static void +check_string (const std::string &spelling, type *ftype, bfd_endian order, + const gdb_mpz &expected) +{ + std::vector actual (ftype->length ()); + SELF_CHECK (target_float_from_string (actual.data (), ftype, spelling)); + if (actual != encode (expected, ftype, order)) + { + gdb_mpz bits; + bits.read (actual, order, true); + gdb_printf (gdb_stdlog, "%s: %s: expected %s, got %s\n", + floatformat_from_type (ftype)->name, spelling.c_str (), + expected.hex ().c_str (), bits.hex ().c_str ()); + } + SELF_CHECK (actual == encode (expected, ftype, order)); +} + +static void +signed_zero_tests () +{ + for (int index = 0; index < ARRAY_SIZE (formats); ++index) + for (bfd_endian order : {BFD_ENDIAN_BIG, BFD_ENDIAN_LITTLE}) + { + type *ftype = make_type (index, order); + gdb_mpz sign = gdb_mpz (1) << (ftype->length () * 8 - 1); + check_string ("0", ftype, order, gdb_mpz (0)); + check_string ("-0", ftype, order, sign); + std::vector zero (ftype->length ()); + target_float_from_host_double (zero.data (), ftype, -0.0); + SELF_CHECK (zero == encode (sign, ftype, order)); + std::vector minus_one (ftype->length ()); + target_float_from_longest (minus_one.data (), ftype, -1); + std::fill (zero.begin (), zero.end (), 0); + target_float_binop (BINOP_MUL, zero.data (), ftype, + minus_one.data (), ftype, zero.data (), ftype); + if (zero != encode (sign, ftype, order)) + gdb_printf (gdb_stdlog, "zero multiply failed: %s\n", + floatformat_from_type (ftype)->name); + SELF_CHECK (zero == encode (sign, ftype, order)); + std::vector negative = encode (sign, ftype, order); + auto *opposite = make_type (index, order == BFD_ENDIAN_BIG + ? BFD_ENDIAN_LITTLE : BFD_ENDIAN_BIG); + std::vector converted (ftype->length ()); + target_float_convert (negative.data (), ftype, + converted.data (), opposite); + SELF_CHECK (converted == encode (sign, opposite, + order == BFD_ENDIAN_BIG + ? BFD_ENDIAN_LITTLE : BFD_ENDIAN_BIG)); + } +} + +static void +subnormal_roundtrip_tests () +{ + for (int index = 0; index < ARRAY_SIZE (formats); ++index) + for (bfd_endian order : {BFD_ENDIAN_BIG, BFD_ENDIAN_LITTLE}) + { + type *ftype = make_type (index, order); + const floatformat &fmt = formats[index][0]; + int quantum = 1 - fmt.exp_bias - static_cast (fmt.man_len); + gdb_mpz normal = gdb_mpz (1) << fmt.man_len; + for (gdb_mpz magnitude : {gdb_mpz (1), gdb_mpz (2), + normal - gdb_mpz (1), normal}) + for (bool negative : {false, true}) + { + gdb_mpz bits = magnitude; + if (negative) + bits = bits | gdb_mpz (1) << (fmt.totalsize - 1); + std::string text = dyadic_spelling (magnitude, quantum); + if (negative) + text.insert (0, "-"); + check_string (text, ftype, order, bits); + auto bytes = encode (bits, ftype, order); + std::string printed + = target_float_to_string (bytes.data (), ftype); + check_string (printed, ftype, order, bits); + bfd_endian reverse_order = order == BFD_ENDIAN_BIG + ? BFD_ENDIAN_LITTLE : BFD_ENDIAN_BIG; + auto *opposite = make_type (index, reverse_order); + std::vector converted (ftype->length ()); + target_float_convert (bytes.data (), ftype, + converted.data (), opposite); + SELF_CHECK (converted == encode (bits, opposite, reverse_order)); + } + } +} + +/* Test ties and values on both sides of each tie, including information that + would be lost if rounded to the normal precision before subnormal rounding. + Use the operation's MPFR ternary result to avoid double rounding. */ +static void +subnormal_rounding_tests () +{ + mpfr_exp_t saved_emin = mpfr_get_emin (), saved_emax = mpfr_get_emax (); + for (int index = 0; index < ARRAY_SIZE (formats); ++index) + for (bfd_endian order : {BFD_ENDIAN_BIG, BFD_ENDIAN_LITTLE}) + { + type *ftype = make_type (index, order); + const floatformat &fmt = formats[index][0]; + int quantum = 1 - fmt.exp_bias - static_cast (fmt.man_len); + int tail = fmt.man_len + 9; + gdb_mpz normal = gdb_mpz (1) << fmt.man_len; + for (gdb_mpz lower : {gdb_mpz (0), gdb_mpz (1), normal - gdb_mpz (1)}) + for (int side : {-1, 0, 1}) + for (bool negative : {false, true}) + { + gdb_mpz coefficient = (lower * gdb_mpz (2) + 1) << (tail - 1); + coefficient += gdb_mpz (side); + gdb_mpz expected = side < 0 ? lower : lower + 1; + if (side == 0 && lower == 0) + expected = 0; + std::string text + = dyadic_spelling (coefficient, quantum - tail); + if (negative) + { + text.insert (0, "-"); + expected = expected | gdb_mpz (1) << (fmt.totalsize - 1); + } + check_string (text, ftype, order, expected); + if (index != 2) + { + /* Binary128 holds these binary32/64 halfway cases exactly. + Converting them exercises the narrowing path's ternary + result independently of parsing at target precision. */ + auto *wide = make_type (2, order); + std::vector source (wide->length ()); + SELF_CHECK (target_float_from_string (source.data (), + wide, text)); + std::vector actual (ftype->length ()); + target_float_convert (source.data (), wide, + actual.data (), ftype); + SELF_CHECK (actual == encode (expected, ftype, order)); + /* The result type can also narrow an arithmetic operation. + Each identity rounds once to normal precision and must + carry its ternary result into subnormal rounding. */ + for (exp_opcode op : {BINOP_ADD, BINOP_SUB, BINOP_MUL, + BINOP_DIV, BINOP_EXP, BINOP_MIN, BINOP_MAX}) + { + std::vector right (wide->length ()); + LONGEST rhs = op == BINOP_ADD || op == BINOP_SUB ? 0 + : op == BINOP_MAX ? -1 : 1; + target_float_from_longest (right.data (), wide, rhs); + target_float_binop (op, source.data (), wide, + right.data (), wide, + actual.data (), ftype); + SELF_CHECK (actual == encode (expected, ftype, order)); + } + } + SELF_CHECK (mpfr_get_emin () == saved_emin); + SELF_CHECK (mpfr_get_emax () == saved_emax); + } + } +} + +/* Exercise unaffected normal and special encodings as well as underflow. */ +static void +normal_tests () +{ + for (int index = 0; index < ARRAY_SIZE (formats); ++index) + for (bfd_endian order : {BFD_ENDIAN_BIG, BFD_ENDIAN_LITTLE}) + { + auto *ftype = make_type (index, order); + const floatformat &fmt = formats[index][0]; + gdb_mpz sign = gdb_mpz (1) << (fmt.totalsize - 1); + gdb_mpz fraction = (gdb_mpz (1) << fmt.man_len) - gdb_mpz (1); + gdb_mpz maximum = (gdb_mpz (fmt.exp_nan - 1) << fmt.man_len) | fraction; + gdb_mpz significand = (gdb_mpz (1) << (fmt.man_len + 1)) - gdb_mpz (1); + int exponent = static_cast (fmt.exp_nan) - 1 - fmt.exp_bias + - static_cast (fmt.man_len); + check_string (dyadic_spelling (significand, exponent), + ftype, order, maximum); + check_string ("-" + dyadic_spelling (significand, exponent), + ftype, order, maximum | sign); + gdb_mpz infinity = gdb_mpz (fmt.exp_nan) << fmt.man_len; + check_string ("inf", ftype, order, infinity); + check_string ("-inf", ftype, order, infinity | sign); + std::vector nan (ftype->length ()); + SELF_CHECK (target_float_from_string (nan.data (), ftype, "nan")); + SELF_CHECK (target_float_is_valid (nan.data (), ftype)); + SELF_CHECK (target_float_to_string (nan.data (), ftype).find ("nan(") + == 0); + } +} + +/* The same encoder handles explicit integer bits, padded and mixed-endian + layouts, and formats without IEEE subnormal or signed-zero support. */ +static void +other_format_tests () +{ + static const floatformat others[] = { + floatformat_i387_ext, floatformat_m68881_ext, + floatformat_ieee_double_littlebyte_bigword, floatformat_vax_f + }; + for (int index = 0; index < ARRAY_SIZE (others); ++index) + { + const floatformat &fmt = others[index]; + const floatformat *pair[] = {&fmt, &fmt}; + type_allocator alloc (current_inferior ()->arch ()); + type *ftype = init_float_type (alloc, -1, "selftest_other_float", pair); + std::vector actual (ftype->length ()); + std::vector expected (ftype->length ()); + SELF_CHECK (target_float_from_string (actual.data (), ftype, "-0")); + if (index != 3) + expected[index == 0 ? 9 : index == 1 ? 0 : 3] = 0x80; + SELF_CHECK (actual == expected); + std::fill (expected.begin (), expected.end (), 0); + if (index == 3) + { + /* Retain the VAX flush-to-zero policy for too-small values. */ + SELF_CHECK (target_float_from_string (actual.data (), + ftype, "0x1p-200")); + SELF_CHECK (actual == expected); + SELF_CHECK (target_float_from_string (actual.data (), + ftype, "0.125")); + expected[1] = 0x3f; + SELF_CHECK (actual == expected); + } + else + { + int precision = fmt.man_len + (fmt.intbit == floatformat_intbit_no); + std::string text + = dyadic_spelling (gdb_mpz (1), 2 - fmt.exp_bias - precision); + SELF_CHECK (target_float_from_string (actual.data (), ftype, text)); + expected[index == 0 ? 0 : index == 1 ? 11 : 4] = 1; + SELF_CHECK (actual == expected); + SELF_CHECK (target_float_is_valid (actual.data (), ftype)); + } + } +} + +} /* namespace target_float */ +} /* namespace selftests */ + +INIT_GDB_FILE (target_float_selftests) +{ + selftests::register_test ("target-float-signed-zero", + selftests::target_float::signed_zero_tests); + selftests::register_test ("target-float-subnormal-roundtrip", + selftests::target_float::subnormal_roundtrip_tests); + selftests::register_test ("target-float-subnormal-rounding", + selftests::target_float::subnormal_rounding_tests); + selftests::register_test ("target-float-normal", + selftests::target_float::normal_tests); + selftests::register_test ("target-float-other-formats", + selftests::target_float::other_format_tests); +} -- 2.34.1