[pushed/v2 3/9] gmp-utils: New API to simply use of GMP's integer/rational/float objects

Joel Brobecker
2020-11-15T08:36:12Z

This API was motivated by a number of reasons:
- GMP's API does not handle "long long" and "unsigned long long",
so using LONGEST and ULONGEST is not straightforward;
- Automate the need to initialize GMP objects before use, and
clear them when no longer used.

However, this API grew also to help with similar matter such
as formatting to a string, and also reading/writing fixed-point
values from byte buffers.

Dedicated unit testing is also added.

gdb/ChangeLog:

* gmp-utils.h, gmp-utils.h: New file.
* unittests/gmp-utils-selftests.c: New file.
* Makefile.in (SUBDIR_UNITTESTS_SRCS): Add
unittests/gmp-utils-selftests.c.
(COMMON_SFILES) Add gmp-utils.c.
(HFILES_NO_SRCDIR): Add gmp-utils.h.
---
gdb/ChangeLog | 9 +
gdb/Makefile.in | 3 +
gdb/gmp-utils.c | 172 ++++++++++++++
gdb/gmp-utils.h | 282 ++++++++++++++++++++++
gdb/unittests/gmp-utils-selftests.c | 460 ++++++++++++++++++++++++++++++++++++
5 files changed, 926 insertions(+)
create mode 100644 gdb/gmp-utils.c
create mode 100644 gdb/gmp-utils.h
create mode 100644 gdb/unittests/gmp-utils-selftests.c

diff --git a/gdb/ChangeLog b/gdb/ChangeLog
index 8de390a..a029109 100644
--- a/gdb/ChangeLog
+++ b/gdb/ChangeLog
@@ -1,5 +1,14 @@
2020-11-15 Joel Brobecker <brobecker@adacore.com>

+ * gmp-utils.h, gmp-utils.h: New file.
+ * unittests/gmp-utils-selftests.c: New file.
+ * Makefile.in (SUBDIR_UNITTESTS_SRCS): Add
+ unittests/gmp-utils-selftests.c.
+ (COMMON_SFILES) Add gmp-utils.c.
+ (HFILES_NO_SRCDIR): Add gmp-utils.h.
+
+2020-11-15 Joel Brobecker <brobecker@adacore.com>
+
* configure.ac: Generate an error if a usable GMP library
could not be found.
* configure: Regenerate.
diff --git a/gdb/Makefile.in b/gdb/Makefile.in
index c461964..9b48f73 100644
--- a/gdb/Makefile.in
+++ b/gdb/Makefile.in
@@ -446,6 +446,7 @@ SELFTESTS_SRCS = \
unittests/filtered_iterator-selftests.c \
unittests/format_pieces-selftests.c \
unittests/function-view-selftests.c \
+ unittests/gmp-utils-selftests.c \
unittests/lookup_name_info-selftests.c \
unittests/memory-map-selftests.c \
unittests/memrange-selftests.c \
@@ -1059,6 +1060,7 @@ COMMON_SFILES = \
gdb_regex.c \
gdbarch.c \
gdbtypes.c \
+ gmp-utils.c \
gnu-v2-abi.c \
gnu-v3-abi.c \
go-lang.c \
@@ -1304,6 +1306,7 @@ HFILES_NO_SRCDIR = \
gdbthread.h \
gdbtypes.h \
glibc-tdep.h \
+ gmp-utils.h \
gnu-nat.h \
go-lang.h \
gregset.h \
diff --git a/gdb/gmp-utils.c b/gdb/gmp-utils.c
new file mode 100644
index 0000000..db92e57
--- /dev/null
+++ b/gdb/gmp-utils.c
@@ -0,0 +1,172 @@
+/* Copyright (C) 2019-2020 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 "gmp-utils.h"
+
+/* See gmp-utils.h. */
+
+gdb::unique_xmalloc_ptr<char>
+gmp_string_asprintf (const char *fmt, ...)
+{
+ va_list vp;
+ char *buf;
+
+ va_start (vp, fmt);
+ gmp_vasprintf (&buf, fmt, vp);
+ va_end (vp);
+
+ return gdb::unique_xmalloc_ptr<char> (buf);
+}
+
+/* See gmp-utils.h. */
+
+void
+gdb_mpz::read (const gdb_byte *buf, int len, enum bfd_endian byte_order,
+ bool unsigned_p)
+{
+ mpz_import (val, 1 /* count */, -1 /* order */, len /* size */,
+ byte_order == BFD_ENDIAN_BIG ? 1 : -1 /* endian */,
+ 0 /* nails */, buf /* op */);
+
+ if (!unsigned_p)
+ {
+ /* The value was imported as if it was a positive value,
+ as mpz_import does not handle signs. If the original value
+ was in fact negative, we need to adjust VAL accordingly. */
+ gdb_mpz max;
+
+ mpz_ui_pow_ui (max.val, 2, len * TARGET_CHAR_BIT - 1);
+ if (mpz_cmp (val, max.val) >= 0)
+ mpz_submul_ui (val, max.val, 2);
+ }
+}
+
+/* See gmp-utils.h. */
+
+void
+gdb_mpz::write (gdb_byte *buf, int len, enum bfd_endian byte_order,
+ bool unsigned_p) const
+{
+ gdb_mpz exported_val (val);
+
+ if (mpz_cmp_ui (val, 0) < 0)
+ {
+ /* mpz_export does not handle signed values, so create a positive
+ value whose bit representation as an unsigned of the same length
+ would be the same as our negative value. */
+ gdb_mpz neg_offset;
+
+ mpz_ui_pow_ui (neg_offset.val, 2, len * TARGET_CHAR_BIT);
+ mpz_add (exported_val.val, exported_val.val, neg_offset.val);
+ }
+
+ /* Start by clearing the buffer, as mpz_export only writes as many
+ bytes as it needs (including none, if the value to export is zero. */
+ memset (buf, 0, len);
+ mpz_export (buf, NULL /* count */, -1 /* order */, len /* size */,
+ byte_order == BFD_ENDIAN_BIG ? 1 : -1 /* endian */,
+ 0 /* nails */, exported_val.val);
+}
+
+/* See gmp-utils.h. */
+
+gdb_mpz
+gdb_mpq::get_rounded () const
+{
+ /* Work with a positive number so as to make the "floor" rounding
+ always round towards zero. */
+
+ gdb_mpq abs_val (val);
+ mpq_abs (abs_val.val, abs_val.val);
+
+ /* Convert our rational number into a quotient and remainder,
+ with "floor" rounding, which in our case means rounding
+ towards zero. */
+
+ gdb_mpz quotient, remainder;
+ mpz_fdiv_qr (quotient.val, remainder.val,
+ mpq_numref (abs_val.val), mpq_denref (abs_val.val));
+
+ /* Multiply the remainder by 2, and see if it is greater or equal
+ to abs_val's denominator. If yes, round to the next integer. */
+
+ mpz_mul_ui (remainder.val, remainder.val, 2);
+ if (mpz_cmp (remainder.val, mpq_denref (abs_val.val)) >= 0)
+ mpz_add_ui (quotient.val, quotient.val, 1);
+
+ /* Re-apply the sign if needed. */
+ if (mpq_sgn (val) < 0)
+ mpz_neg (quotient.val, quotient.val);
+
+ return quotient;
+}
+
+/* See gmp-utils.h. */
+
+void
+gdb_mpq::read_fixed_point (const gdb_byte *buf, int len,
+ enum bfd_endian byte_order, bool unsigned_p,
+ const gdb_mpq &scaling_factor)
+{
+ gdb_mpz vz;
+ vz.read (buf, len, byte_order, unsigned_p);
+
+ mpq_set_z (val, vz.val);
+ mpq_mul (val, val, scaling_factor.val);
+}
+
+/* See gmp-utils.h. */
+
+void
+gdb_mpq::write_fixed_point (gdb_byte *buf, int len,
+ enum bfd_endian byte_order, bool unsigned_p,
+ const gdb_mpq &scaling_factor) const
+{
+ gdb_mpq unscaled (val);
+
+ mpq_div (unscaled.val, unscaled.val, scaling_factor.val);
+
+ gdb_mpz unscaled_z = unscaled.get_rounded ();
+ unscaled_z.write (buf, len, byte_order, unsigned_p);
+}
+
+/* A wrapper around xrealloc that we can then register with GMP
+ as the "realloc" function. */
+
+static void *
+xrealloc_for_gmp (void *ptr, size_t old_size, size_t new_size)
+{
+ return xrealloc (ptr, new_size);
+}
+
+/* A wrapper around xfree that we can then register with GMP
+ as the "free" function. */
+
+static void
+xfree_for_gmp (void *ptr, size_t size)
+{
+ xfree (ptr);
+}
+
+void _initialize_gmp_utils ();
+
+void
+_initialize_gmp_utils ()
+{
+ /* Tell GMP to use GDB's memory management routines. */
+ mp_set_memory_functions (xmalloc, xrealloc_for_gmp, xfree_for_gmp);
+}
diff --git a/gdb/gmp-utils.h b/gdb/gmp-utils.h
new file mode 100644
index 0000000..1214b64
--- /dev/null
+++ b/gdb/gmp-utils.h
@@ -0,0 +1,282 @@
+/* Miscellaneous routines making it easier to use GMP within GDB's framework.
+
+ Copyright (C) 2019-2020 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/>. */
+
+#ifndef GMP_UTILS_H
+#define GMP_UTILS_H
+
+#include "defs.h"
+
+/* Include <stdio.h> and <stdarg.h> ahead of <gmp.h>, so as to get
+ access to GMP's various formatting functions. */
+#include <stdio.h>
+#include <stdarg.h>
+#include <gmp.h>
+#include "gdbsupport/traits.h"
+
+/* Same as gmp_asprintf, but returning a convenient wrapper type. */
+
+gdb::unique_xmalloc_ptr<char> gmp_string_asprintf (const char *fmt, ...);
+
+/* A class to make it easier to use GMP's mpz_t values within GDB. */
+
+struct gdb_mpz
+{
+ mpz_t val;
+
+ /* Constructors. */
+ gdb_mpz () { mpz_init (val); }
+
+ explicit gdb_mpz (const mpz_t &from_val)
+ {
+ mpz_init (val);
+ mpz_set (val, from_val);
+ }
+
+ gdb_mpz (const gdb_mpz &from)
+ {
+ mpz_init (val);
+ mpz_set (val, from.val);
+ }
+
+ /* Initialize using the given integral value.
+
+ The main advantage of this method is that it handles both signed
+ and unsigned types, with no size restriction. */
+ template<typename T, typename = gdb::Requires<std::is_integral<T>>>
+ explicit gdb_mpz (T src)
+ {
+ mpz_init (val);
+ set (src);
+ }
+
+ explicit gdb_mpz (gdb_mpz &&from)
+ {
+ mpz_init (val);
+ mpz_swap (val, from.val);
+ }
+
+
+ gdb_mpz &operator= (const gdb_mpz &from)
+ {
+ mpz_set (val, from.val);
+ return *this;
+ }
+
+ gdb_mpz &operator= (gdb_mpz &&other)
+ {
+ mpz_swap (val, other.val);
+ return *this;
+ }
+
+ template<typename T, typename = gdb::Requires<std::is_integral<T>>>
+ gdb_mpz &operator= (T src)
+ {
+ set (src);
+ return *this;
+ }
+
+ /* Convert VAL to an integer of the given type.
+
+ The return type can signed or unsigned, with no size restriction. */
+ template<typename T> T as_integer () const;
+
+ /* Set VAL by importing the number stored in the byte buffer (BUF),
+ given its size (LEN) and BYTE_ORDER.
+
+ UNSIGNED_P indicates whether the number has an unsigned type. */
+ void read (const gdb_byte *buf, int len, enum bfd_endian byte_order,
+ bool unsigned_p);
+
+ /* Write VAL into BUF as a LEN-bytes number with the given BYTE_ORDER.
+
+ UNSIGNED_P indicates whether the number has an unsigned type. */
+ void write (gdb_byte *buf, int len, enum bfd_endian byte_order,
+ bool unsigned_p) const;
+
+ /* Return a string containing VAL. */
+ gdb::unique_xmalloc_ptr<char> str () const
+ { return gmp_string_asprintf ("%Zd", val); }
+
+ /* The destructor. */
+ ~gdb_mpz () { mpz_clear (val); }
+
+private:
+
+ /* Helper template for constructor and operator=. */
+ template<typename T> void set (T src);
+};
+
+/* A class to make it easier to use GMP's mpq_t values within GDB. */
+
+struct gdb_mpq
+{
+ mpq_t val;
+
+ /* Constructors. */
+ gdb_mpq () { mpq_init (val); }
+
+ explicit gdb_mpq (const mpq_t &from_val)
+ {
+ mpq_init (val);
+ mpq_set (val, from_val);
+ }
+
+ gdb_mpq (const gdb_mpq &from)
+ {
+ mpq_init (val);
+ mpq_set (val, from.val);
+ }
+
+ explicit gdb_mpq (gdb_mpq &&from)
+ {
+ mpq_init (val);
+ mpq_swap (val, from.val);
+ }
+
+ /* Copy assignment operator. */
+ gdb_mpq &operator= (const gdb_mpq &from)
+ {
+ mpq_set (val, from.val);
+ return *this;
+ }
+
+ gdb_mpq &operator= (gdb_mpq &&from)
+ {
+ mpq_swap (val, from.val);
+ return *this;
+ }
+
+ /* Return a string representing VAL as "<numerator> / <denominator>". */
+ gdb::unique_xmalloc_ptr<char> str () const
+ { return gmp_string_asprintf ("%Qd", val); }
+
+ /* Return VAL rounded to the nearest integer. */
+ gdb_mpz get_rounded () const;
+
+ /* Set VAL from the contents of the given buffer (BUF), which
+ contains the unscaled value of a fixed point type object
+ with the given size (LEN) and byte order (BYTE_ORDER).
+
+ UNSIGNED_P indicates whether the number has an unsigned type.
+ SCALING_FACTOR is the scaling factor to apply after having
+ read the unscaled value from our buffer. */
+ void read_fixed_point (const gdb_byte *buf, int len,
+ enum bfd_endian byte_order, bool unsigned_p,
+ const gdb_mpq &scaling_factor);
+
+ /* Write VAL into BUF as a LEN-bytes fixed point value following
+ the given BYTE_ORDER.
+
+ UNSIGNED_P indicates whether the number has an unsigned type.
+ SCALING_FACTOR is the scaling factor to apply before writing
+ the unscaled value to our buffer. */
+ void write_fixed_point (gdb_byte *buf, int len,
+ enum bfd_endian byte_order, bool unsigned_p,
+ const gdb_mpq &scaling_factor) const;
+
+ /* The destructor. */
+ ~gdb_mpq () { mpq_clear (val); }
+};
+
+/* A class to make it easier to use GMP's mpf_t values within GDB.
+
+ Should MPFR become a required dependency, we should probably
+ drop this class in favor of using MPFR. */
+
+struct gdb_mpf
+{
+ mpf_t val;
+
+ /* Constructors. */
+ gdb_mpf () { mpf_init (val); }
+
+ DISABLE_COPY_AND_ASSIGN (gdb_mpf);
+
+ /* Set VAL from the contents of the given buffer (BUF), which
+ contains the unscaled value of a fixed point type object
+ with the given size (LEN) and byte order (BYTE_ORDER).
+
+ UNSIGNED_P indicates whether the number has an unsigned type.
+ SCALING_FACTOR is the scaling factor to apply after having
+ read the unscaled value from our buffer. */
+ void read_fixed_point (const gdb_byte *buf, int len,
+ enum bfd_endian byte_order, bool unsigned_p,
+ const gdb_mpq &scaling_factor)
+ {
+ gdb_mpq tmp_q;
+
+ tmp_q.read_fixed_point (buf, len, byte_order, unsigned_p, scaling_factor);
+ mpf_set_q (val, tmp_q.val);
+ }
+
+ /* The destructor. */
+ ~gdb_mpf () { mpf_clear (val); }
+};
+
+/* See declaration above. */
+
+template<typename T>
+void
+gdb_mpz::set (T src)
+{
+ mpz_import (val, 1 /* count */, -1 /* order */,
+ sizeof (T) /* size */, 0 /* endian (0 = native) */,
+ 0 /* nails */, &src /* op */);
+ if (std::is_signed<T>::value && src < 0)
+ {
+ /* mpz_import does not handle the sign, so our value was imported
+ as an unsigned. Adjust that imported value so as to make it
+ the correct negative value. */
+ gdb_mpz neg_offset;
+
+ mpz_ui_pow_ui (neg_offset.val, 2, sizeof (T) * HOST_CHAR_BIT);
+ mpz_sub (val, val, neg_offset.val);
+ }
+}
+
+/* See declaration above. */
+
+template<typename T>
+T
+gdb_mpz::as_integer () const
+{
+ /* Initialize RESULT, because mpz_export only write the minimum
+ number of bytes, including none if our value is zero! */
+ T result = 0;
+
+ gdb_mpz exported_val (val);
+ if (std::is_signed<T>::value && mpz_cmp_ui (val, 0) < 0)
+ {
+ /* We want to use mpz_export to set the return value, but
+ this function does not handle the sign. So give exported_val
+ a value which is at the same time positive, and has the same
+ bit representation as our negative value. */
+ gdb_mpz neg_offset;
+
+ mpz_ui_pow_ui (neg_offset.val, 2, sizeof (T) * HOST_CHAR_BIT);
+ mpz_add (exported_val.val, exported_val.val, neg_offset.val);
+ }
+
+ mpz_export (&result, NULL /* count */, -1 /* order */,
+ sizeof (T) /* size */, 0 /* endian (0 = native) */,
+ 0 /* nails */, exported_val.val);
+ return result;
+}
+
+#endif
diff --git a/gdb/unittests/gmp-utils-selftests.c b/gdb/unittests/gmp-utils-selftests.c
new file mode 100644
index 0000000..e8c3c5c
--- /dev/null
+++ b/gdb/unittests/gmp-utils-selftests.c
@@ -0,0 +1,460 @@
+/* Self tests of the gmp-utils API.
+
+ Copyright (C) 2019-2020 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 "gmp-utils.h"
+#include "gdbsupport/selftest.h"
+
+#include <math.h>
+
+namespace selftests {
+
+/* Perform a series of general tests of gdb_mpz's as_integer method.
+
+ This function tries to be reasonably exhaustive, by testing the edges,
+ as well as a resonable set of values including negative ones, zero,
+ and positive values. */
+
+static void
+gdb_mpz_as_integer ()
+{
+ /* Test a range of values, both as LONGEST and ULONGEST. */
+ gdb_mpz v;
+ LONGEST l_expected;
+ ULONGEST ul_expected;
+
+ /* Start with the smallest LONGEST */
+ l_expected = (LONGEST) 1 << (sizeof (LONGEST) * 8 - 1);
+
+ mpz_ui_pow_ui (v.val, 2, sizeof (LONGEST) * 8 - 1);
+ mpz_neg (v.val, v.val);
+
+ SELF_CHECK (v.as_integer<LONGEST> () == l_expected);
+
+ /* Try with a small range of integers including negative, zero,
+ and positive values. */
+ for (int i = -256; i <= 256; i++)
+ {
+ l_expected = (LONGEST) i;
+ mpz_set_si (v.val, i);
+ SELF_CHECK (v.as_integer<LONGEST> () == l_expected);
+
+ if (i >= 0)
+ {
+ ul_expected = (ULONGEST) i;
+ mpz_set_ui (v.val, i);
+ SELF_CHECK (v.as_integer<ULONGEST> () == ul_expected);
+ }
+ }
+
+ /* Try with LONGEST_MAX. */
+ l_expected = LONGEST_MAX;
+ ul_expected = (ULONGEST) l_expected;
+
+ mpz_ui_pow_ui (v.val, 2, sizeof (LONGEST) * 8 - 1);
+ mpz_sub_ui (v.val, v.val, 1);
+
+ SELF_CHECK (v.as_integer<LONGEST> () == l_expected);
+ SELF_CHECK (v.as_integer<ULONGEST> () == ul_expected);
+
+ /* Try with ULONGEST_MAX. */
+ ul_expected = ULONGEST_MAX;
+ mpz_ui_pow_ui (v.val, 2, sizeof (LONGEST) * 8);
+ mpz_sub_ui (v.val, v.val, 1);
+
+ SELF_CHECK (v.as_integer<ULONGEST> () == ul_expected);
+}
+
+/* A helper function to store the given integer value into a buffer,
+ before reading it back into a gdb_mpz. Sets ACTUAL to the value
+ read back, while at the same time setting EXPECTED as the value
+ we would expect to be read back.
+
+ Note that this function does not perform the comparison between
+ EXPECTED and ACTUAL. The caller will do it inside a SELF_CHECK
+ call, allowing the line information shown when the test fails
+ to provide a bit more information about the kind of values
+ that were used when the check failed. This makes the writing
+ of the tests a little more verbose, but the debugging in case
+ of problems should hopefuly be easier. */
+
+template<typename T>
+void
+store_and_read_back (T val, int buf_len, enum bfd_endian byte_order,
+ gdb_mpz &expected, gdb_mpz &actual)
+{
+ gdb_byte *buf;
+
+ expected = val;
+
+ buf = (gdb_byte *) alloca (buf_len);
+ store_integer (buf, buf_len, byte_order, val);
+
+ /* Pre-initialize ACTUAL to something that's not the expected value. */
+ mpz_set (actual.val, expected.val);
+ mpz_sub_ui (actual.val, actual.val, 500);
+
+ actual.read (buf, buf_len, byte_order, !std::is_signed<T>::value);
+}
+
+/* Test the gdb_mpz::read method over a reasonable range of values.
+
+ The testing is done by picking an arbitrary buffer length, after
+ which we test every possible value that this buffer allows, both
+ with signed numbers as well as unsigned ones. */
+
+static void
+gdb_mpz_read_all_from_small ()
+{
+ /* Start with a type whose size is small enough that we can afford
+ to check the complete range. */
+
+ int buf_len = 1;
+ LONGEST l_min = -pow (2, buf_len * 8 - 1);
+ LONGEST l_max = pow (2, buf_len * 8 - 1) - 1;
+
+ for (LONGEST l = l_min; l <= l_max; l++)
+ {
+ gdb_mpz expected, actual;
+
+ store_and_read_back (l, buf_len, BFD_ENDIAN_BIG, expected, actual);
+ SELF_CHECK (mpz_cmp (actual.val, expected.val) == 0);
+
+ store_and_read_back (l, buf_len, BFD_ENDIAN_LITTLE, expected, actual);
+ SELF_CHECK (mpz_cmp (actual.val, expected.val) == 0);
+ }
+
+ /* Do the same as above, but with an unsigned type. */
+ ULONGEST ul_min = 0;
+ ULONGEST ul_max = pow (2, buf_len * 8) - 1;
+
+ for (ULONGEST ul = ul_min; ul <= ul_max; ul++)
+ {
+ gdb_mpz expected, actual;
+
+ store_and_read_back (ul, buf_len, BFD_ENDIAN_BIG, expected, actual);
+ SELF_CHECK (mpz_cmp (actual.val, expected.val) == 0);
+
+ store_and_read_back (ul, buf_len, BFD_ENDIAN_LITTLE, expected, actual);
+ SELF_CHECK (mpz_cmp (actual.val, expected.val) == 0);
+ }
+}
+
+/* Test the gdb_mpz::read the extremes of LONGEST and ULONGEST. */
+
+static void
+gdb_mpz_read_min_max ()
+{
+ gdb_mpz expected, actual;
+
+ /* Start with the smallest LONGEST. */
+
+ LONGEST l_min = (LONGEST) 1 << (sizeof (LONGEST) * 8 - 1);
+
+ store_and_read_back (l_min, sizeof (LONGEST), BFD_ENDIAN_BIG,
+ expected, actual);
+ SELF_CHECK (mpz_cmp (actual.val, expected.val) == 0);
+
+ store_and_read_back (l_min, sizeof (LONGEST), BFD_ENDIAN_LITTLE,
+ expected, actual);
+ SELF_CHECK (mpz_cmp (actual.val, expected.val) == 0);
+
+ /* Same with LONGEST_MAX. */
+
+ LONGEST l_max = LONGEST_MAX;
+
+ store_and_read_back (l_max, sizeof (LONGEST), BFD_ENDIAN_BIG,
+ expected, actual);
+ SELF_CHECK (mpz_cmp (actual.val, expected.val) == 0);
+
+ store_and_read_back (l_max, sizeof (LONGEST), BFD_ENDIAN_LITTLE,
+ expected, actual);
+ SELF_CHECK (mpz_cmp (actual.val, expected.val) == 0);
+
+ /* Same with the smallest ULONGEST. */
+
+ ULONGEST ul_min = 0;
+
+ store_and_read_back (ul_min, sizeof (ULONGEST), BFD_ENDIAN_BIG,
+ expected, actual);
+ SELF_CHECK (mpz_cmp (actual.val, expected.val) == 0);
+
+ store_and_read_back (ul_min, sizeof (ULONGEST), BFD_ENDIAN_LITTLE,
+ expected, actual);
+ SELF_CHECK (mpz_cmp (actual.val, expected.val) == 0);
+
+ /* Same with ULONGEST_MAX. */
+
+ ULONGEST ul_max = ULONGEST_MAX;
+
+ store_and_read_back (ul_max, sizeof (ULONGEST), BFD_ENDIAN_BIG,
+ expected, actual);
+ SELF_CHECK (mpz_cmp (actual.val, expected.val) == 0);
+
+ store_and_read_back (ul_max, sizeof (ULONGEST), BFD_ENDIAN_LITTLE,
+ expected, actual);
+ SELF_CHECK (mpz_cmp (actual.val, expected.val) == 0);
+}
+
+/* A helper function which creates a gdb_mpz object from the given
+ integer VAL, and then writes it using its gdb_mpz::write method.
+
+ The written value is then extracted from the buffer and returned,
+ for comparison with the original.
+
+ Note that this function does not perform the comparison between
+ VAL and the returned value. The caller will do it inside a SELF_CHECK
+ call, allowing the line information shown when the test fails
+ to provide a bit more information about the kind of values
+ that were used when the check failed. This makes the writing
+ of the tests a little more verbose, but the debugging in case
+ of problems should hopefuly be easier. */
+
+template<typename T>
+T
+write_and_extract (T val, int buf_len, enum bfd_endian byte_order)
+{
+ gdb_mpz v (val);
+
+ SELF_CHECK (v.as_integer<T> () == val);
+
+ gdb_byte *buf = (gdb_byte *) alloca (buf_len);
+ v.write (buf, buf_len, byte_order, !std::is_signed<T>::value);
+
+ return extract_integer<T> (buf, buf_len, byte_order);
+}
+
+/* Test the gdb_mpz::write method over a reasonable range of values.
+
+ The testing is done by picking an arbitrary buffer length, after
+ which we test every possible value that this buffer allows. */
+
+static void
+gdb_mpz_write_all_from_small ()
+{
+ int buf_len = 1;
+ LONGEST l_min = -pow (2, buf_len * 8 - 1);
+ LONGEST l_max = pow (2, buf_len * 8 - 1) - 1;
+
+ for (LONGEST l = l_min; l <= l_max; l++)
+ {
+ SELF_CHECK (write_and_extract (l, buf_len, BFD_ENDIAN_BIG) == l);
+ SELF_CHECK (write_and_extract (l, buf_len, BFD_ENDIAN_LITTLE) == l);
+ }
+
+ /* Do the same as above, but with an unsigned type. */
+ ULONGEST ul_min = 0;
+ ULONGEST ul_max = pow (2, buf_len * 8) - 1;
+
+ for (ULONGEST ul = ul_min; ul <= ul_max; ul++)
+ {
+ SELF_CHECK (write_and_extract (ul, buf_len, BFD_ENDIAN_BIG) == ul);
+ SELF_CHECK (write_and_extract (ul, buf_len, BFD_ENDIAN_LITTLE) == ul);
+ }
+}
+
+/* Test the gdb_mpz::write the extremes of LONGEST and ULONGEST. */
+
+static void
+gdb_mpz_write_min_max ()
+{
+ /* Start with the smallest LONGEST. */
+
+ LONGEST l_min = (LONGEST) 1 << (sizeof (LONGEST) * 8 - 1);
+ SELF_CHECK (write_and_extract (l_min, sizeof (LONGEST), BFD_ENDIAN_BIG)
+ == l_min);
+ SELF_CHECK (write_and_extract (l_min, sizeof (LONGEST), BFD_ENDIAN_LITTLE)
+ == l_min);
+
+ /* Same with LONGEST_MAX. */
+
+ LONGEST l_max = LONGEST_MAX;
+ SELF_CHECK (write_and_extract (l_max, sizeof (LONGEST), BFD_ENDIAN_BIG)
+ == l_max);
+ SELF_CHECK (write_and_extract (l_max, sizeof (LONGEST), BFD_ENDIAN_LITTLE)
+ == l_max);
+
+ /* Same with the smallest ULONGEST. */
+
+ ULONGEST ul_min = (ULONGEST) 1 << (sizeof (ULONGEST) * 8 - 1);
+ SELF_CHECK (write_and_extract (ul_min, sizeof (ULONGEST), BFD_ENDIAN_BIG)
+ == ul_min);
+ SELF_CHECK (write_and_extract (ul_min, sizeof (ULONGEST), BFD_ENDIAN_LITTLE)
+ == ul_min);
+
+ /* Same with ULONGEST_MAX. */
+
+ ULONGEST ul_max = ULONGEST_MAX;
+ SELF_CHECK (write_and_extract (ul_max, sizeof (ULONGEST), BFD_ENDIAN_BIG)
+ == ul_max);
+ SELF_CHECK (write_and_extract (ul_max, sizeof (ULONGEST), BFD_ENDIAN_LITTLE)
+ == ul_max);
+}
+
+/* A helper function which stores the signed number, the unscaled value
+ of a fixed point object, into a buffer, and then uses gdb_mpq's
+ read_fixed_point to read it as a fixed_point value, with
+ the given parameters.
+
+ EXPECTED is set to the value we expected to get after the call
+ to read_fixed_point. ACTUAL is the value we actually do get.
+
+ Note that this function does not perform the comparison between
+ EXPECTED and ACTUAL. The caller will do it inside a SELF_CHECK
+ call, allowing the line information shown when the test fails
+ to provide a bit more information about the kind of values
+ that were used when the check failed. This makes the writing
+ of the tests a little more verbose, but the debugging in case
+ of problems should hopefuly be easier. */
+
+static void
+read_fp_test (int unscaled, const gdb_mpq &scaling_factor,
+ enum bfd_endian byte_order,
+ gdb_mpq &expected, gdb_mpq &actual)
+{
+ /* For this kind of testing, we'll use a buffer the same size as
+ our unscaled parameter. */
+ const int len = sizeof (unscaled);
+ gdb_byte buf[len];
+ store_signed_integer (buf, len, byte_order, unscaled);
+
+ actual.read_fixed_point (buf, len, byte_order, 0, scaling_factor);
+
+ mpq_set_si (expected.val, unscaled, 1);
+ mpq_mul (expected.val, expected.val, scaling_factor.val);
+}
+
+/* Perform various tests of the gdb_mpq::read_fixed_point method. */
+
+static void
+gdb_mpq_read_fixed_point ()
+{
+ gdb_mpq expected, actual;
+ gdb_mpq scaling_factor;
+
+ /* Pick an arbitrary scaling_factor; this operation is trivial enough
+ thanks to GMP that the value we use isn't really important. */
+ mpq_set_ui (scaling_factor.val, 3, 5);
+
+ /* Try a few values, both negative and positive... */
+
+ read_fp_test (-256, scaling_factor, BFD_ENDIAN_BIG, expected, actual);
+ SELF_CHECK (mpq_cmp (actual.val, expected.val) == 0);
+ read_fp_test (-256, scaling_factor, BFD_ENDIAN_LITTLE, expected, actual);
+ SELF_CHECK (mpq_cmp (actual.val, expected.val) == 0);
+
+ read_fp_test (-1, scaling_factor, BFD_ENDIAN_BIG, expected, actual);
+ SELF_CHECK (mpq_cmp (actual.val, expected.val) == 0);
+ read_fp_test (-1, scaling_factor, BFD_ENDIAN_LITTLE, expected, actual);
+ SELF_CHECK (mpq_cmp (actual.val, expected.val) == 0);
+
+ read_fp_test (0, scaling_factor, BFD_ENDIAN_BIG, expected, actual);
+ SELF_CHECK (mpq_cmp (actual.val, expected.val) == 0);
+ read_fp_test (0, scaling_factor, BFD_ENDIAN_LITTLE, expected, actual);
+ SELF_CHECK (mpq_cmp (actual.val, expected.val) == 0);
+
+ read_fp_test (1, scaling_factor, BFD_ENDIAN_BIG, expected, actual);
+ SELF_CHECK (mpq_cmp (actual.val, expected.val) == 0);
+ read_fp_test (1, scaling_factor, BFD_ENDIAN_LITTLE, expected, actual);
+ SELF_CHECK (mpq_cmp (actual.val, expected.val) == 0);
+
+ read_fp_test (1025, scaling_factor, BFD_ENDIAN_BIG, expected, actual);
+ SELF_CHECK (mpq_cmp (actual.val, expected.val) == 0);
+ read_fp_test (1025, scaling_factor, BFD_ENDIAN_LITTLE, expected, actual);
+ SELF_CHECK (mpq_cmp (actual.val, expected.val) == 0);
+}
+
+/* A helper function which builds a gdb_mpq object from the given
+ NUMERATOR and DENOMINATOR, and then calls gdb_mpq's write_fixed_point
+ method to write it to a buffer.
+
+ The value written into the buffer is then read back as is,
+ and returned. */
+
+static LONGEST
+write_fp_test (int numerator, unsigned int denominator,
+ const gdb_mpq &scaling_factor,
+ enum bfd_endian byte_order)
+{
+ /* For this testing, we'll use a buffer the size of LONGEST.
+ This is really an arbitrary decision, as long as the buffer
+ is long enough to hold the unscaled values that we'll be
+ writing. */
+ const int len = sizeof (LONGEST);
+ gdb_byte buf[len];
+ memset (buf, 0, len);
+
+ gdb_mpq v;
+ mpq_set_ui (v.val, numerator, denominator);
+ mpq_canonicalize (v.val);
+ v.write_fixed_point (buf, len, byte_order, 0, scaling_factor);
+
+ return extract_unsigned_integer (buf, len, byte_order);
+}
+
+/* Perform various tests of the gdb_mpq::write_fixed_point method. */
+
+static void
+gdb_mpq_write_fixed_point ()
+{
+ /* Pick an arbitrary factor; this operations is sufficiently trivial
+ with the use of GMP that the value of this factor is not really
+ all that important. */
+ gdb_mpq scaling_factor;
+ mpq_set_ui (scaling_factor.val, 1, 3);
+
+ gdb_mpq vq;
+
+ /* Try a few multiples of the scaling factor, both negative,
+ and positive... */
+
+ SELF_CHECK (write_fp_test (-8, 1, scaling_factor, BFD_ENDIAN_BIG) == -24);
+ SELF_CHECK (write_fp_test (-8, 1, scaling_factor, BFD_ENDIAN_LITTLE) == -24);
+
+ SELF_CHECK (write_fp_test (-2, 3, scaling_factor, BFD_ENDIAN_BIG) == -2);
+ SELF_CHECK (write_fp_test (-2, 3, scaling_factor, BFD_ENDIAN_LITTLE) == -2);
+
+ SELF_CHECK (write_fp_test (0, 3, scaling_factor, BFD_ENDIAN_BIG) == 0);
+ SELF_CHECK (write_fp_test (0, 3, scaling_factor, BFD_ENDIAN_LITTLE) == 0);
+
+ SELF_CHECK (write_fp_test (5, 3, scaling_factor, BFD_ENDIAN_BIG) == 5);
+ SELF_CHECK (write_fp_test (5, 3, scaling_factor, BFD_ENDIAN_LITTLE) == 5);
+}
+
+}
+
+void _initialize_gmp_utils_selftests ();
+
+void
+_initialize_gmp_utils_selftests ()
+{
+ selftests::register_test ("gdb_mpz_as_integer",
+ selftests::gdb_mpz_as_integer);
+ selftests::register_test ("gdb_mpz_read_all_from_small",
+ selftests::gdb_mpz_read_all_from_small);
+ selftests::register_test ("gdb_mpz_read_min_max",
+ selftests::gdb_mpz_read_min_max);
+ selftests::register_test ("gdb_mpz_write_all_from_small",
+ selftests::gdb_mpz_write_all_from_small);
+ selftests::register_test ("gdb_mpz_write_min_max",
+ selftests::gdb_mpz_write_min_max);
+ selftests::register_test ("gdb_mpq_read_fixed_point",
+ selftests::gdb_mpq_read_fixed_point);
+ selftests::register_test ("gdb_mpq_write_fixed_point",
+ selftests::gdb_mpq_write_fixed_point);
+}
--
2.1.4