* [PATCH] gdb: Fix signed-zero and subnormal conversions in target floating-point support
@ 2026-10-28 16:33 Robert Dubner
0 siblings, 0 replies; only message in thread
From: Robert Dubner @ 2026-10-28 16:33 UTC (permalink / raw)
To: gdb-patches
From 461b097d2319f771a5ffb28273782115b3017e13 Mon Sep 17 00:00:00 2001
From: Robert Dubner <rdubner@symas.com>
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 <http://www.gnu.org/licenses/>.
*/
+
+#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 <mpfr.h>
+
+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<gdb_byte>
+encode (const gdb_mpz &bits, type *ftype, bfd_endian order)
+{
+ std::vector<gdb_byte> 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<gdb_byte> 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<gdb_byte> zero (ftype->length ());
+ target_float_from_host_double (zero.data (), ftype, -0.0);
+ SELF_CHECK (zero == encode (sign, ftype, order));
+ std::vector<gdb_byte> 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<gdb_byte> negative = encode (sign, ftype, order);
+ auto *opposite = make_type (index, order == BFD_ENDIAN_BIG
+ ? BFD_ENDIAN_LITTLE :
BFD_ENDIAN_BIG);
+ std::vector<gdb_byte> 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<int> (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<gdb_byte> 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<int> (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<gdb_byte> source (wide->length ());
+ SELF_CHECK (target_float_from_string (source.data (),
+ wide, text));
+ std::vector<gdb_byte> 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<gdb_byte> 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<int> (fmt.exp_nan) - 1 - fmt.exp_bias
+ - static_cast<int> (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<gdb_byte> 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<gdb_byte> actual (ftype->length ());
+ std::vector<gdb_byte> 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
^ permalink raw reply [flat|nested] only message in thread
only message in thread, other threads:[~2026-10-28 16:34 UTC | newest]
Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-28 16:33 [PATCH] gdb: Fix signed-zero and subnormal conversions in target floating-point support Robert Dubner
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox