mirror of
https://sourceware.org/git/binutils-gdb.git
synced 2024-11-23 10:03:47 +08:00
gmp-utils: New API to simply use of GMP's integer/rational/float objects
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.
This commit is contained in:
parent
1b4ac058f7
commit
b34c74ab9a
@ -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
|
||||
|
@ -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 \
|
||||
|
172
gdb/gmp-utils.c
Normal file
172
gdb/gmp-utils.c
Normal file
@ -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);
|
||||
}
|
282
gdb/gmp-utils.h
Normal file
282
gdb/gmp-utils.h
Normal file
@ -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
|
460
gdb/unittests/gmp-utils-selftests.c
Normal file
460
gdb/unittests/gmp-utils-selftests.c
Normal file
@ -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);
|
||||
}
|
Loading…
Reference in New Issue
Block a user