/* Floating point routines for GDB, the GNU debugger.
Copyright (C) 2017 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 . */
#include "defs.h"
#include "dfp.h"
#include "doublest.h"
#include "gdbtypes.h"
#include "floatformat.h"
#include "target-float.h"
/* Helper routines operating on binary floating-point data. */
#include
/* Convert the byte-stream ADDR, interpreted as floating-point format FMT,
to an integer value (rounding towards zero). */
static LONGEST
floatformat_to_longest (const struct floatformat *fmt, const gdb_byte *addr)
{
DOUBLEST d;
floatformat_to_doublest (fmt, addr, &d);
return (LONGEST) d;
}
/* Convert signed integer VAL to a target floating-number of format FMT
and store it as byte-stream ADDR. */
static void
floatformat_from_longest (const struct floatformat *fmt, gdb_byte *addr,
LONGEST val)
{
DOUBLEST d = (DOUBLEST) val;
floatformat_from_doublest (fmt, &d, addr);
}
/* Convert unsigned integer VAL to a target floating-number of format FMT
and store it as byte-stream ADDR. */
static void
floatformat_from_ulongest (const struct floatformat *fmt, gdb_byte *addr,
ULONGEST val)
{
DOUBLEST d = (DOUBLEST) val;
floatformat_from_doublest (fmt, &d, addr);
}
/* Convert the byte-stream ADDR, interpreted as floating-point format FMT,
to a floating-point value in the host "double" format. */
static double
floatformat_to_host_double (const struct floatformat *fmt,
const gdb_byte *addr)
{
DOUBLEST d;
floatformat_to_doublest (fmt, addr, &d);
return (double) d;
}
/* Convert floating-point value VAL in the host "double" format to a target
floating-number of format FMT and store it as byte-stream ADDR. */
static void
floatformat_from_host_double (const struct floatformat *fmt, gdb_byte *addr,
double val)
{
DOUBLEST d = (DOUBLEST) val;
floatformat_from_doublest (fmt, &d, addr);
}
/* Convert a floating-point number of format FROM_FMT from the target
byte-stream FROM to a floating-point number of format TO_FMT, and
store it to the target byte-stream TO. */
static void
floatformat_convert (const gdb_byte *from, const struct floatformat *from_fmt,
gdb_byte *to, const struct floatformat *to_fmt)
{
if (from_fmt == to_fmt)
{
/* The floating-point formats match, so we simply copy the data. */
memcpy (to, from, floatformat_totalsize_bytes (to_fmt));
}
else
{
/* The floating-point formats don't match. The best we can do
(apart from simulating the target FPU) is converting to the
widest floating-point type supported by the host, and then
again to the desired type. */
DOUBLEST d;
floatformat_to_doublest (from_fmt, from, &d);
floatformat_from_doublest (to_fmt, &d, to);
}
}
/* Perform the binary operation indicated by OPCODE, using as operands the
target byte streams X and Y, interpreted as floating-point numbers of
formats FMT_X and FMT_Y, respectively. Convert the result to format
FMT_RES and store it into the byte-stream RES. */
static void
floatformat_binop (enum exp_opcode op,
const struct floatformat *fmt_x, const gdb_byte *x,
const struct floatformat *fmt_y, const gdb_byte *y,
const struct floatformat *fmt_result, gdb_byte *result)
{
DOUBLEST v1, v2, v = 0;
floatformat_to_doublest (fmt_x, x, &v1);
floatformat_to_doublest (fmt_y, y, &v2);
switch (op)
{
case BINOP_ADD:
v = v1 + v2;
break;
case BINOP_SUB:
v = v1 - v2;
break;
case BINOP_MUL:
v = v1 * v2;
break;
case BINOP_DIV:
v = v1 / v2;
break;
case BINOP_EXP:
errno = 0;
v = pow (v1, v2);
if (errno)
error (_("Cannot perform exponentiation: %s"),
safe_strerror (errno));
break;
case BINOP_MIN:
v = v1 < v2 ? v1 : v2;
break;
case BINOP_MAX:
v = v1 > v2 ? v1 : v2;
break;
default:
error (_("Integer-only operation on floating point number."));
break;
}
floatformat_from_doublest (fmt_result, &v, result);
}
/* Compare the two target byte streams X and Y, interpreted as floating-point
numbers of formats FMT_X and FMT_Y, respectively. Return zero if X and Y
are equal, -1 if X is less than Y, and 1 otherwise. */
static int
floatformat_compare (const struct floatformat *fmt_x, const gdb_byte *x,
const struct floatformat *fmt_y, const gdb_byte *y)
{
DOUBLEST v1, v2;
floatformat_to_doublest (fmt_x, x, &v1);
floatformat_to_doublest (fmt_y, y, &v2);
if (v1 == v2)
return 0;
if (v1 < v2)
return -1;
return 1;
}
/* Typed floating-point routines. These routines operate on floating-point
values in target format, represented by a byte buffer interpreted as a
"struct type", which may be either a binary or decimal floating-point
type (TYPE_CODE_FLT or TYPE_CODE_DECFLOAT). */
/* Return whether the byte-stream ADDR holds a valid value of
floating-point type TYPE. */
bool
target_float_is_valid (const gdb_byte *addr, const struct type *type)
{
if (TYPE_CODE (type) == TYPE_CODE_FLT)
return floatformat_is_valid (floatformat_from_type (type), addr);
if (TYPE_CODE (type) == TYPE_CODE_DECFLOAT)
return true;
gdb_assert_not_reached ("unexpected type code");
}
/* Return whether the byte-stream ADDR, interpreted as floating-point
type TYPE, is numerically equal to zero (of either sign). */
bool
target_float_is_zero (const gdb_byte *addr, const struct type *type)
{
if (TYPE_CODE (type) == TYPE_CODE_FLT)
return (floatformat_classify (floatformat_from_type (type), addr)
== float_zero);
if (TYPE_CODE (type) == TYPE_CODE_DECFLOAT)
return decimal_is_zero (addr, TYPE_LENGTH (type),
gdbarch_byte_order (get_type_arch (type)));
gdb_assert_not_reached ("unexpected type code");
}
/* Convert the byte-stream ADDR, interpreted as floating-point type TYPE,
to a string, optionally using the print format FORMAT. */
std::string
target_float_to_string (const gdb_byte *addr, const struct type *type,
const char *format)
{
if (TYPE_CODE (type) == TYPE_CODE_FLT)
return floatformat_to_string (floatformat_from_type (type), addr, format);
if (TYPE_CODE (type) == TYPE_CODE_DECFLOAT)
return decimal_to_string (addr, TYPE_LENGTH (type),
gdbarch_byte_order (get_type_arch (type)),
format);
gdb_assert_not_reached ("unexpected type code");
}
/* Parse string STRING into a target floating-number of type TYPE and
store it as byte-stream ADDR. Return whether parsing succeeded. */
bool
target_float_from_string (gdb_byte *addr, const struct type *type,
const std::string &string)
{
/* Ensure possible padding bytes in the target buffer are zeroed out. */
memset (addr, 0, TYPE_LENGTH (type));
if (TYPE_CODE (type) == TYPE_CODE_FLT)
return floatformat_from_string (floatformat_from_type (type), addr,
string);
if (TYPE_CODE (type) == TYPE_CODE_DECFLOAT)
return decimal_from_string (addr, TYPE_LENGTH (type),
gdbarch_byte_order (get_type_arch (type)),
string);
gdb_assert_not_reached ("unexpected type code");
}
/* Convert the byte-stream ADDR, interpreted as floating-point type TYPE,
to an integer value (rounding towards zero). */
LONGEST
target_float_to_longest (const gdb_byte *addr, const struct type *type)
{
if (TYPE_CODE (type) == TYPE_CODE_FLT)
return floatformat_to_longest (floatformat_from_type (type), addr);
if (TYPE_CODE (type) == TYPE_CODE_DECFLOAT)
return decimal_to_longest (addr, TYPE_LENGTH (type),
gdbarch_byte_order (get_type_arch (type)));
gdb_assert_not_reached ("unexpected type code");
}
/* Convert signed integer VAL to a target floating-number of type TYPE
and store it as byte-stream ADDR. */
void
target_float_from_longest (gdb_byte *addr, const struct type *type,
LONGEST val)
{
/* Ensure possible padding bytes in the target buffer are zeroed out. */
memset (addr, 0, TYPE_LENGTH (type));
if (TYPE_CODE (type) == TYPE_CODE_FLT)
{
floatformat_from_longest (floatformat_from_type (type), addr, val);
return;
}
if (TYPE_CODE (type) == TYPE_CODE_DECFLOAT)
{
decimal_from_longest (val, addr, TYPE_LENGTH (type),
gdbarch_byte_order (get_type_arch (type)));
return;
}
gdb_assert_not_reached ("unexpected type code");
}
/* Convert unsigned integer VAL to a target floating-number of type TYPE
and store it as byte-stream ADDR. */
void
target_float_from_ulongest (gdb_byte *addr, const struct type *type,
ULONGEST val)
{
/* Ensure possible padding bytes in the target buffer are zeroed out. */
memset (addr, 0, TYPE_LENGTH (type));
if (TYPE_CODE (type) == TYPE_CODE_FLT)
{
floatformat_from_ulongest (floatformat_from_type (type), addr, val);
return;
}
if (TYPE_CODE (type) == TYPE_CODE_DECFLOAT)
{
decimal_from_ulongest (val, addr, TYPE_LENGTH (type),
gdbarch_byte_order (get_type_arch (type)));
return;
}
gdb_assert_not_reached ("unexpected type code");
}
/* Convert the byte-stream ADDR, interpreted as floating-point type TYPE,
to a floating-point value in the host "double" format. */
double
target_float_to_host_double (const gdb_byte *addr,
const struct type *type)
{
if (TYPE_CODE (type) == TYPE_CODE_FLT)
return floatformat_to_host_double (floatformat_from_type (type), addr);
/* We don't support conversions between target decimal floating-point
types and the host double type here. */
gdb_assert_not_reached ("unexpected type code");
}
/* Convert floating-point value VAL in the host "double" format to a target
floating-number of type TYPE and store it as byte-stream ADDR. */
void
target_float_from_host_double (gdb_byte *addr, const struct type *type,
double val)
{
/* Ensure possible padding bytes in the target buffer are zeroed out. */
memset (addr, 0, TYPE_LENGTH (type));
if (TYPE_CODE (type) == TYPE_CODE_FLT)
{
floatformat_from_host_double (floatformat_from_type (type), addr, val);
return;
}
/* We don't support conversions between target decimal floating-point
types and the host double type here. */
gdb_assert_not_reached ("unexpected type code");
}
/* Convert a floating-point number of type FROM_TYPE from the target
byte-stream FROM to a floating-point number of type TO_TYPE, and
store it to the target byte-stream TO. */
void
target_float_convert (const gdb_byte *from, const struct type *from_type,
gdb_byte *to, const struct type *to_type)
{
/* Ensure possible padding bytes in the target buffer are zeroed out. */
memset (to, 0, TYPE_LENGTH (to_type));
/* Use direct conversion routines if we have them. */
if (TYPE_CODE (from_type) == TYPE_CODE_FLT
&& TYPE_CODE (to_type) == TYPE_CODE_FLT)
{
floatformat_convert (from, floatformat_from_type (from_type),
to, floatformat_from_type (to_type));
return;
}
if (TYPE_CODE (from_type) == TYPE_CODE_DECFLOAT
&& TYPE_CODE (to_type) == TYPE_CODE_DECFLOAT)
{
decimal_convert (from, TYPE_LENGTH (from_type),
gdbarch_byte_order (get_type_arch (from_type)),
to, TYPE_LENGTH (to_type),
gdbarch_byte_order (get_type_arch (to_type)));
return;
}
/* We cannot directly convert between binary and decimal floating-point
types, so go via an intermediary string. */
if ((TYPE_CODE (from_type) == TYPE_CODE_FLT
&& TYPE_CODE (to_type) == TYPE_CODE_DECFLOAT)
|| (TYPE_CODE (from_type) == TYPE_CODE_DECFLOAT
&& TYPE_CODE (to_type) == TYPE_CODE_FLT))
{
std::string str = target_float_to_string (from, from_type);
target_float_from_string (to, to_type, str);
return;
}
gdb_assert_not_reached ("unexpected type code");
}
/* Perform the binary operation indicated by OPCODE, using as operands the
target byte streams X and Y, interpreted as floating-point numbers of
types TYPE_X and TYPE_Y, respectively. Convert the result to type
TYPE_RES and store it into the byte-stream RES.
The three types must either be all binary floating-point types, or else
all decimal floating-point types. Binary and decimal floating-point
types cannot be mixed within a single operation. */
void
target_float_binop (enum exp_opcode opcode,
const gdb_byte *x, const struct type *type_x,
const gdb_byte *y, const struct type *type_y,
gdb_byte *res, const struct type *type_res)
{
/* Ensure possible padding bytes in the target buffer are zeroed out. */
memset (res, 0, TYPE_LENGTH (type_res));
if (TYPE_CODE (type_res) == TYPE_CODE_FLT)
{
gdb_assert (TYPE_CODE (type_x) == TYPE_CODE_FLT);
gdb_assert (TYPE_CODE (type_y) == TYPE_CODE_FLT);
return floatformat_binop (opcode,
floatformat_from_type (type_x), x,
floatformat_from_type (type_y), y,
floatformat_from_type (type_res), res);
}
if (TYPE_CODE (type_res) == TYPE_CODE_DECFLOAT)
{
gdb_assert (TYPE_CODE (type_x) == TYPE_CODE_DECFLOAT);
gdb_assert (TYPE_CODE (type_y) == TYPE_CODE_DECFLOAT);
return decimal_binop (opcode,
x, TYPE_LENGTH (type_x),
gdbarch_byte_order (get_type_arch (type_x)),
y, TYPE_LENGTH (type_y),
gdbarch_byte_order (get_type_arch (type_y)),
res, TYPE_LENGTH (type_res),
gdbarch_byte_order (get_type_arch (type_res)));
}
gdb_assert_not_reached ("unexpected type code");
}
/* Compare the two target byte streams X and Y, interpreted as floating-point
numbers of types TYPE_X and TYPE_Y, respectively. Return zero if X and Y
are equal, -1 if X is less than Y, and 1 otherwise.
The two types must either both be binary floating-point types, or else
both be decimal floating-point types. Binary and decimal floating-point
types cannot compared directly against each other. */
int
target_float_compare (const gdb_byte *x, const struct type *type_x,
const gdb_byte *y, const struct type *type_y)
{
if (TYPE_CODE (type_x) == TYPE_CODE_FLT)
{
gdb_assert (TYPE_CODE (type_y) == TYPE_CODE_FLT);
return floatformat_compare (floatformat_from_type (type_x), x,
floatformat_from_type (type_y), y);
}
if (TYPE_CODE (type_x) == TYPE_CODE_DECFLOAT)
{
gdb_assert (TYPE_CODE (type_y) == TYPE_CODE_DECFLOAT);
return decimal_compare (x, TYPE_LENGTH (type_x),
gdbarch_byte_order (get_type_arch (type_x)),
y, TYPE_LENGTH (type_y),
gdbarch_byte_order (get_type_arch (type_y)));
}
gdb_assert_not_reached ("unexpected type code");
}