* dwarf2.c (struct line_head) [total_length]: Change type to
[binutils-gdb.git] / gdb / doublest.c
index af635f223cb822cd4781cb2e3919618426c634d0..083a50a98aa1f2dc4570f2a9c6d1ec64b2f95bc4 100644 (file)
@@ -31,6 +31,7 @@
 #include "floatformat.h"
 #include "gdb_assert.h"
 #include "gdb_string.h"
+#include "gdbtypes.h"
 #include <math.h>              /* ldexp */
 
 /* The odds that CHAR_BIT will be anything but 8 are low enough that I'm not
@@ -104,10 +105,10 @@ get_field (unsigned char *data, enum floatformat_byteorders order,
    FROM is the address of the extended float.
    Store the DOUBLEST in *TO.  */
 
-void
-floatformat_to_doublest (const struct floatformat *fmt,
-                        const void *from,
-                        DOUBLEST *to)
+static void
+convert_floatformat_to_doublest (const struct floatformat *fmt,
+                                const void *from,
+                                DOUBLEST *to)
 {
   unsigned char *ufrom = (unsigned char *) from;
   DOUBLEST dto;
@@ -325,10 +326,10 @@ ldfrexp (long double value, int *eptr)
    and store where TO points.  Neither FROM nor TO have any alignment
    restrictions.  */
 
-void
-floatformat_from_doublest (CONST struct floatformat *fmt,
-                          const DOUBLEST *from,
-                          void *to)
+static void
+convert_doublest_to_floatformat (CONST struct floatformat *fmt,
+                                const DOUBLEST *from,
+                                void *to)
 {
   DOUBLEST dfrom;
   int exponent;
@@ -531,99 +532,251 @@ floatformat_mantissa (const struct floatformat *fmt, char *val)
   return res;
 }
 
-
 \f
-/* Extract a floating-point number from a target-order byte-stream at ADDR.
-   Returns the value as type DOUBLEST.
+/* Convert TO/FROM target to the hosts DOUBLEST floating-point format.
 
-   If the host and target formats agree, we just copy the raw data into the
-   appropriate type of variable and return, letting the host increase precision
-   as necessary.  Otherwise, we call the conversion routine and let it do the
-   dirty work.  */
+   If the host and target formats agree, we just copy the raw data
+   into the appropriate type of variable and return, letting the host
+   increase precision as necessary.  Otherwise, we call the conversion
+   routine and let it do the dirty work.  */
 
-DOUBLEST
-extract_floating (const void *addr, int len)
-{
-  DOUBLEST dretval;
+#ifndef HOST_FLOAT_FORMAT
+#define HOST_FLOAT_FORMAT 0
+#endif
+#ifndef HOST_DOUBLE_FORMAT
+#define HOST_DOUBLE_FORMAT 0
+#endif
+#ifndef HOST_LONG_DOUBLE_FORMAT
+#define HOST_LONG_DOUBLE_FORMAT 0
+#endif
 
-  if (len * TARGET_CHAR_BIT == TARGET_FLOAT_BIT)
-    {
-      if (HOST_FLOAT_FORMAT == TARGET_FLOAT_FORMAT)
-       {
-         float retval;
+static const struct floatformat *host_float_format = HOST_FLOAT_FORMAT;
+static const struct floatformat *host_double_format = HOST_DOUBLE_FORMAT;
+static const struct floatformat *host_long_double_format = HOST_LONG_DOUBLE_FORMAT;
 
-         memcpy (&retval, addr, sizeof (retval));
-         return retval;
-       }
-      else
-       floatformat_to_doublest (TARGET_FLOAT_FORMAT, addr, &dretval);
+void
+floatformat_to_doublest (const struct floatformat *fmt,
+                        const void *in, DOUBLEST *out)
+{
+  gdb_assert (fmt != NULL);
+  if (fmt == host_float_format)
+    {
+      float val;
+      memcpy (&val, in, sizeof (val));
+      *out = val;
     }
-  else if (len * TARGET_CHAR_BIT == TARGET_DOUBLE_BIT)
+  else if (fmt == host_double_format)
     {
-      if (HOST_DOUBLE_FORMAT == TARGET_DOUBLE_FORMAT)
-       {
-         double retval;
-
-         memcpy (&retval, addr, sizeof (retval));
-         return retval;
-       }
-      else
-       floatformat_to_doublest (TARGET_DOUBLE_FORMAT, addr, &dretval);
+      double val;
+      memcpy (&val, in, sizeof (val));
+      *out = val;
     }
-  else if (len * TARGET_CHAR_BIT == TARGET_LONG_DOUBLE_BIT)
+  else if (fmt == host_long_double_format)
     {
-      if (HOST_LONG_DOUBLE_FORMAT == TARGET_LONG_DOUBLE_FORMAT)
-       {
-         DOUBLEST retval;
+      long double val;
+      memcpy (&val, in, sizeof (val));
+      *out = val;
+    }
+  else
+    convert_floatformat_to_doublest (fmt, in, out);
+}
 
-         memcpy (&retval, addr, sizeof (retval));
-         return retval;
-       }
-      else
-       floatformat_to_doublest (TARGET_LONG_DOUBLE_FORMAT, addr, &dretval);
+void
+floatformat_from_doublest (const struct floatformat *fmt,
+                          const DOUBLEST *in, void *out)
+{
+  gdb_assert (fmt != NULL);
+  if (fmt == host_float_format)
+    {
+      float val = *in;
+      memcpy (out, &val, sizeof (val));
+    }
+  else if (fmt == host_double_format)
+    {
+      double val = *in;
+      memcpy (out, &val, sizeof (val));
+    }
+  else if (fmt == host_long_double_format)
+    {
+      long double val = *in;
+      memcpy (out, &val, sizeof (val));
     }
   else
+    convert_doublest_to_floatformat (fmt, in, out);
+}
+
+\f
+/* Return a floating-point format for a floating-point variable of
+   length LEN.  Return NULL, if no suitable floating-point format
+   could be found.
+
+   We need this functionality since information about the
+   floating-point format of a type is not always available to GDB; the
+   debug information typically only tells us the size of a
+   floating-point type.
+
+   FIXME: kettenis/2001-10-28: In many places, particularly in
+   target-dependent code, the format of floating-point types is known,
+   but not passed on by GDB.  This should be fixed.  */
+
+static const struct floatformat *
+floatformat_from_length (int len)
+{
+  if (len * TARGET_CHAR_BIT == TARGET_FLOAT_BIT)
+    return TARGET_FLOAT_FORMAT;
+  else if (len * TARGET_CHAR_BIT == TARGET_DOUBLE_BIT)
+    return TARGET_DOUBLE_FORMAT;
+  else if (len * TARGET_CHAR_BIT == TARGET_LONG_DOUBLE_BIT)
+    return TARGET_LONG_DOUBLE_FORMAT;
+
+  return NULL;
+}
+
+/* If the host doesn't define NAN, use zero instead.  */
+#ifndef NAN
+#define NAN 0.0
+#endif
+
+/* Extract a floating-point number of length LEN from a target-order
+   byte-stream at ADDR.  Returns the value as type DOUBLEST.  */
+
+DOUBLEST
+extract_floating (const void *addr, int len)
+{
+  const struct floatformat *fmt = floatformat_from_length (len);
+  DOUBLEST val;
+
+  if (fmt == NULL)
     {
-      error ("Can't deal with a floating point number of %d bytes.", len);
+      warning ("Can't store a floating-point number of %d bytes.", len);
+      return NAN;
     }
 
-  return dretval;
+  floatformat_to_doublest (fmt, addr, &val);
+  return val;
 }
 
+/* Store VAL as a floating-point number of length LEN to a
+   target-order byte-stream at ADDR.  */
+
 void
 store_floating (void *addr, int len, DOUBLEST val)
 {
-  if (len * TARGET_CHAR_BIT == TARGET_FLOAT_BIT)
-    {
-      if (HOST_FLOAT_FORMAT == TARGET_FLOAT_FORMAT)
-       {
-         float floatval = val;
+  const struct floatformat *fmt = floatformat_from_length (len);
 
-         memcpy (addr, &floatval, sizeof (floatval));
-       }
-      else
-       floatformat_from_doublest (TARGET_FLOAT_FORMAT, &val, addr);
-    }
-  else if (len * TARGET_CHAR_BIT == TARGET_DOUBLE_BIT)
+  if (fmt == NULL)
     {
-      if (HOST_DOUBLE_FORMAT == TARGET_DOUBLE_FORMAT)
-       {
-         double doubleval = val;
+      warning ("Can't store a floating-point number of %d bytes.", len);
+      memset (addr, 0, len);
+    }
 
-         memcpy (addr, &doubleval, sizeof (doubleval));
-       }
-      else
-       floatformat_from_doublest (TARGET_DOUBLE_FORMAT, &val, addr);
+  floatformat_from_doublest (fmt, &val, addr);
+}
+
+/* Extract a floating-point number of type TYPE from a target-order
+   byte-stream at ADDR.  Returns the value as type DOUBLEST.  */
+
+DOUBLEST
+extract_typed_floating (const void *addr, const struct type *type)
+{
+  DOUBLEST retval;
+
+  gdb_assert (TYPE_CODE (type) == TYPE_CODE_FLT);
+
+  if (TYPE_FLOATFORMAT (type) == NULL)
+    return extract_floating (addr, TYPE_LENGTH (type));
+
+  floatformat_to_doublest (TYPE_FLOATFORMAT (type), addr, &retval);
+  return retval;
+}
+
+/* Store VAL as a floating-point number of type TYPE to a target-order
+   byte-stream at ADDR.  */
+
+void
+store_typed_floating (void *addr, const struct type *type, DOUBLEST val)
+{
+  gdb_assert (TYPE_CODE (type) == TYPE_CODE_FLT);
+
+  /* FIXME: kettenis/2001-10-28: It is debatable whether we should
+     zero out any remaining bytes in the target buffer when TYPE is
+     longer than the actual underlying floating-point format.  Perhaps
+     we should store a fixed bitpattern in those remaining bytes,
+     instead of zero, or perhaps we shouldn't touch those remaining
+     bytes at all.
+
+     NOTE: cagney/2001-10-28: With the way things currently work, it
+     isn't a good idea to leave the end bits undefined.  This is
+     because GDB writes out the entire sizeof(<floating>) bits of the
+     floating-point type even though the value might only be stored
+     in, and the target processor may only refer to, the first N <
+     TYPE_LENGTH (type) bits.  If the end of the buffer wasn't
+     initialized, GDB would write undefined data to the target.  An
+     errant program, refering to that undefined data, would then
+     become non-deterministic.
+
+     See also the function convert_typed_floating below.  */
+  memset (addr, 0, TYPE_LENGTH (type));
+
+  if (TYPE_FLOATFORMAT (type) == NULL)
+    return store_floating (addr, TYPE_LENGTH (type), val);
+
+  floatformat_from_doublest (TYPE_FLOATFORMAT (type), &val, addr);
+}
+
+/* Convert a floating-point number of type FROM_TYPE from a
+   target-order byte-stream at FROM to a floating-point number of type
+   TO_TYPE, and store it to a target-order byte-stream at TO.  */
+
+void
+convert_typed_floating (const void *from, const struct type *from_type,
+                        void *to, const struct type *to_type)
+{
+  const struct floatformat *from_fmt = TYPE_FLOATFORMAT (from_type);
+  const struct floatformat *to_fmt = TYPE_FLOATFORMAT (to_type);
+
+  gdb_assert (TYPE_CODE (from_type) == TYPE_CODE_FLT);
+  gdb_assert (TYPE_CODE (to_type) == TYPE_CODE_FLT);
+
+  /* If the floating-point format of FROM_TYPE or TO_TYPE isn't known,
+     try to guess it from the type's length.  */
+  if (from_fmt == NULL)
+    from_fmt = floatformat_from_length (TYPE_LENGTH (from_type));
+  if (to_fmt == NULL)
+    to_fmt = floatformat_from_length (TYPE_LENGTH (to_type));
+
+  if (from_fmt == NULL || to_fmt == NULL)
+    {
+      /* If we don't know the floating-point format of FROM_TYPE or
+         TO_TYPE, there's not much we can do.  We might make the
+         assumption that if the length of FROM_TYPE and TO_TYPE match,
+         their floating-point format would match too, but that
+         assumption might be wrong on targets that support
+         floating-point types that only differ in endianness for
+         example.  So we warn instead, and zero out the target buffer.  */
+      warning ("Can't convert floating-point number to desired type.");
+      memset (to, 0, TYPE_LENGTH (to_type));
     }
-  else if (len * TARGET_CHAR_BIT == TARGET_LONG_DOUBLE_BIT)
+  else if (from_fmt == to_fmt)
     {
-      if (HOST_LONG_DOUBLE_FORMAT == TARGET_LONG_DOUBLE_FORMAT)
-       memcpy (addr, &val, sizeof (val));
-      else
-       floatformat_from_doublest (TARGET_LONG_DOUBLE_FORMAT, &val, addr);
+      /* We're in business.  The floating-point format of FROM_TYPE
+         and TO_TYPE match.  However, even though the floating-point
+         format matches, the length of the type might still be
+         different.  Make sure we don't overrun any buffers.  See
+         comment in store_typed_floating for a discussion about
+         zeroing out remaining bytes in the target buffer.  */
+      memset (to, 0, TYPE_LENGTH (to_type));
+      memcpy (to, from, min (TYPE_LENGTH (from_type), TYPE_LENGTH (to_type)));
     }
   else
     {
-      error ("Can't deal with a floating point number of %d bytes.", len);
+      /* The floating-point types don't match.  The best we can do
+         (aport 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);
     }
 }