gmp-utils: New API to simply use of GMP's integer/rational/float objects
authorJoel Brobecker <brobecker@adacore.com>
Sun, 15 Nov 2020 08:09:44 +0000 (03:09 -0500)
committerJoel Brobecker <brobecker@adacore.com>
Sun, 15 Nov 2020 08:09:44 +0000 (03:09 -0500)
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
gdb/Makefile.in
gdb/gmp-utils.c [new file with mode: 0644]
gdb/gmp-utils.h [new file with mode: 0644]
gdb/unittests/gmp-utils-selftests.c [new file with mode: 0644]

index 8de390a7c729f68132118861b09f31fede2aa2c1..a0291092e29e538bc06c409cd0c259678cba2ae1 100644 (file)
@@ -1,3 +1,12 @@
+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
index c461964837fdd7c0487eb7b60bb73f0f72000d8b..9b48f7375306c966e83079bd63485155f532a62d 100644 (file)
@@ -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 (file)
index 0000000..db92e57
--- /dev/null
@@ -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 (file)
index 0000000..1214b64
--- /dev/null
@@ -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 (file)
index 0000000..e8c3c5c
--- /dev/null
@@ -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);
+}