1/* Copyright (C) 2019-2023 Free Software Foundation, Inc.
2
3   This file is part of GDB.
4
5   This program is free software; you can redistribute it and/or modify
6   it under the terms of the GNU General Public License as published by
7   the Free Software Foundation; either version 3 of the License, or
8   (at your option) any later version.
9
10   This program is distributed in the hope that it will be useful,
11   but WITHOUT ANY WARRANTY; without even the implied warranty of
12   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   GNU General Public License for more details.
14
15   You should have received a copy of the GNU General Public License
16   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
17
18#include "gmp-utils.h"
19
20/* See gmp-utils.h.  */
21
22std::string
23gmp_string_printf (const char *fmt, ...)
24{
25  va_list vp;
26
27  va_start (vp, fmt);
28  int size = gmp_vsnprintf (NULL, 0, fmt, vp);
29  va_end (vp);
30
31  std::string str (size, '\0');
32
33  /* C++11 and later guarantee std::string uses contiguous memory and
34     always includes the terminating '\0'.  */
35  va_start (vp, fmt);
36  gmp_vsprintf (&str[0], fmt, vp);
37  va_end (vp);
38
39  return str;
40}
41
42/* See gmp-utils.h.  */
43
44void
45gdb_mpz::read (gdb::array_view<const gdb_byte> buf, enum bfd_endian byte_order,
46	       bool unsigned_p)
47{
48  mpz_import (val, 1 /* count */, -1 /* order */, buf.size () /* size */,
49	      byte_order == BFD_ENDIAN_BIG ? 1 : -1 /* endian */,
50	      0 /* nails */, buf.data () /* op */);
51
52  if (!unsigned_p)
53    {
54      /* The value was imported as if it was a positive value,
55	 as mpz_import does not handle signs. If the original value
56	 was in fact negative, we need to adjust VAL accordingly.  */
57      gdb_mpz max;
58
59      mpz_ui_pow_ui (max.val, 2, buf.size () * HOST_CHAR_BIT - 1);
60      if (mpz_cmp (val, max.val) >= 0)
61	mpz_submul_ui (val, max.val, 2);
62    }
63}
64
65/* See gmp-utils.h.  */
66
67void
68gdb_mpz::write (gdb::array_view<gdb_byte> buf, enum bfd_endian byte_order,
69		bool unsigned_p) const
70{
71  this->safe_export
72    (buf, byte_order == BFD_ENDIAN_BIG ? 1 : -1 /* endian */, unsigned_p);
73}
74
75/* See gmp-utils.h.  */
76
77void
78gdb_mpz::safe_export (gdb::array_view<gdb_byte> buf,
79		      int endian, bool unsigned_p) const
80{
81  gdb_assert (buf.size () > 0);
82
83  if (mpz_sgn (val) == 0)
84    {
85      /* Our value is zero, so no need to call mpz_export to do the work,
86	 especially since mpz_export's documentation explicitly says
87	 that the function is a noop in this case.  Just write zero to
88	 BUF ourselves.  */
89      memset (buf.data (), 0, buf.size ());
90      return;
91    }
92
93  /* Determine the maximum range of values that our buffer can hold,
94     and verify that VAL is within that range.  */
95
96  gdb_mpz lo, hi;
97  const size_t max_usable_bits = buf.size () * HOST_CHAR_BIT;
98  if (unsigned_p)
99    {
100      lo = 0;
101
102      mpz_ui_pow_ui (hi.val, 2, max_usable_bits);
103      mpz_sub_ui (hi.val, hi.val, 1);
104    }
105  else
106    {
107      mpz_ui_pow_ui (lo.val, 2, max_usable_bits - 1);
108      mpz_neg (lo.val, lo.val);
109
110      mpz_ui_pow_ui (hi.val, 2, max_usable_bits - 1);
111      mpz_sub_ui (hi.val, hi.val, 1);
112    }
113
114  if (mpz_cmp (val, lo.val) < 0 || mpz_cmp (val, hi.val) > 0)
115    error (_("Cannot export value %s as %zu-bits %s integer"
116	     " (must be between %s and %s)"),
117	   this->str ().c_str (),
118	   max_usable_bits,
119	   unsigned_p ? _("unsigned") : _("signed"),
120	   lo.str ().c_str (),
121	   hi.str ().c_str ());
122
123  gdb_mpz exported_val (val);
124
125  if (mpz_cmp_ui (exported_val.val, 0) < 0)
126    {
127      /* mpz_export does not handle signed values, so create a positive
128	 value whose bit representation as an unsigned of the same length
129	 would be the same as our negative value.  */
130      gdb_mpz neg_offset;
131
132      mpz_ui_pow_ui (neg_offset.val, 2, buf.size () * HOST_CHAR_BIT);
133      mpz_add (exported_val.val, exported_val.val, neg_offset.val);
134    }
135
136  /* Do the export into a buffer allocated by GMP itself; that way,
137     we can detect cases where BUF is not large enough to export
138     our value, and thus avoid a buffer overlow.  Normally, this should
139     never happen, since we verified earlier that the buffer is large
140     enough to accomodate our value, but doing this allows us to be
141     extra safe with the export.
142
143     After verification that the export behaved as expected, we will
144     copy the data over to BUF.  */
145
146  size_t word_countp;
147  gdb::unique_xmalloc_ptr<void> exported
148    (mpz_export (NULL, &word_countp, -1 /* order */, buf.size () /* size */,
149		 endian, 0 /* nails */, exported_val.val));
150
151  gdb_assert (word_countp == 1);
152
153  memcpy (buf.data (), exported.get (), buf.size ());
154}
155
156/* See gmp-utils.h.  */
157
158gdb_mpz
159gdb_mpq::get_rounded () const
160{
161  /* Work with a positive number so as to make the "floor" rounding
162     always round towards zero.  */
163
164  gdb_mpq abs_val (val);
165  mpq_abs (abs_val.val, abs_val.val);
166
167  /* Convert our rational number into a quotient and remainder,
168     with "floor" rounding, which in our case means rounding
169     towards zero.  */
170
171  gdb_mpz quotient, remainder;
172  mpz_fdiv_qr (quotient.val, remainder.val,
173	       mpq_numref (abs_val.val), mpq_denref (abs_val.val));
174
175  /* Multiply the remainder by 2, and see if it is greater or equal
176     to abs_val's denominator.  If yes, round to the next integer.  */
177
178  mpz_mul_ui (remainder.val, remainder.val, 2);
179  if (mpz_cmp (remainder.val, mpq_denref (abs_val.val)) >= 0)
180    mpz_add_ui (quotient.val, quotient.val, 1);
181
182  /* Re-apply the sign if needed.  */
183  if (mpq_sgn (val) < 0)
184    mpz_neg (quotient.val, quotient.val);
185
186  return quotient;
187}
188
189/* See gmp-utils.h.  */
190
191void
192gdb_mpq::read_fixed_point (gdb::array_view<const gdb_byte> buf,
193			   enum bfd_endian byte_order, bool unsigned_p,
194			   const gdb_mpq &scaling_factor)
195{
196  gdb_mpz vz;
197  vz.read (buf, byte_order, unsigned_p);
198
199  mpq_set_z (val, vz.val);
200  mpq_mul (val, val, scaling_factor.val);
201}
202
203/* See gmp-utils.h.  */
204
205void
206gdb_mpq::write_fixed_point (gdb::array_view<gdb_byte> buf,
207			    enum bfd_endian byte_order, bool unsigned_p,
208			    const gdb_mpq &scaling_factor) const
209{
210  gdb_mpq unscaled (val);
211
212  mpq_div (unscaled.val, unscaled.val, scaling_factor.val);
213
214  gdb_mpz unscaled_z = unscaled.get_rounded ();
215  unscaled_z.write (buf, byte_order, unsigned_p);
216}
217
218/* A wrapper around xrealloc that we can then register with GMP
219   as the "realloc" function.  */
220
221static void *
222xrealloc_for_gmp (void *ptr, size_t old_size, size_t new_size)
223{
224  return xrealloc (ptr, new_size);
225}
226
227/* A wrapper around xfree that we can then register with GMP
228   as the "free" function.  */
229
230static void
231xfree_for_gmp (void *ptr, size_t size)
232{
233  xfree (ptr);
234}
235
236void _initialize_gmp_utils ();
237
238void
239_initialize_gmp_utils ()
240{
241  /* Tell GMP to use GDB's memory management routines.  */
242  mp_set_memory_functions (xmalloc, xrealloc_for_gmp, xfree_for_gmp);
243}
244