diff mbox

[09/24] MIPS: Enhance cooked FP format

Message ID 1467038991-6600-9-git-send-email-bhushan.attarde@imgtec.com
State New
Headers show

Commit Message

Bhushan Attarde June 27, 2016, 2:49 p.m. UTC
Define cooked FP types as unions representing the possible interpretations
    of the registers.

    Previously the type of the cooked floating point registers depended
    directly on the raw type, so for MIPS32 targets they were single
    precision, and for MIPS64 targets they were single or double precision
    depending on the FP mode.

    However in both cases it is possible for the FP registers to be
    interpreted as either single or double precision values depending on the
    instruction executed, and in a different way depending on the FP mode.

    When FR=0:
    - Even FP registers can be interpreted as a double (aliasing both odd and
      even singles), or a single (aliasing the least significant half of the
      double).
    - Odd FP registers can be interpreted only as a single (aliasing the most
      significant half of the even double).

    When FR=1:
    - All FP registers can be interpreted as a double, or a single (aliasing
      the least significant half of the double).

    Some instructions can also treat them as 32-bit or 64-bit integers for the
    purposes of converting between formats.

    Therefore define two cooked fp types, as follows.

    The first type, fp32, is used for odd FP registers when FR=0, which can
    only be interpreted as singles or 32-bit integers. It is equivalent to
    this C definition:
      union fp32 {
        float f32;
        int32_t i32;
      };

    The second type, fp64, is used for even FP registers when FR=0, and all FP
    registers when FR=1, which can be interpreted as either singles or
    doubles, with the single always in the least significant half of the
    double. It is roughly equivalent to this C definition (when interpreted as
    little endian):
      union fp64 {
        float  f32;
        double f64;
        int32  i32;
        int64  i64;
      };

    Since multiple bits of code need to handle the different raw register
    layouts and these new cooked types for different modes, the details are
    abstracted with the following functions:
    - mips_get_fp_single_location: Gets the raw register and offset containing
      a single precision register value.
    - mips_get_fp_double_location: Gets the raw register(s) and offset(s)
      composing a double precision register value.
    - mips_get_fp_multi_location: Gets the raw register(s) and offset(s)
      composing a cooked register value.

    These abstractions are used by the code which reads and writes the cooked
    (pseudo) FP registers, and the code which prints out the FP registers
    ("info float").

    Now that the cooked type may be composed of multiple raw registers (where
    only single precision raw values are provided), there is no need to
    provide conversion functions from the 32-bit cooked value to double.
    Instead the opposite is required for extracting a single out of the 64-bit
    cooked value.

    For example, with FR=0:
      (gdb) info float
      ...
      f30: 0x41f00000 flt: 30                dbl: 6442451998.9999998
      f31: 0x41f80000 flt: 31
      (gdb) p $f30
      $1 = {f32 = 30, f64 = 6442451998.9999998,
            i32 = 1106247680, i64 = 4753549407795806208}
      (gdb) p $f31
      $2 = {f32 = 31, i32 = 1106771968}

    And with FR=1:
      (gdb) info float
      ...
      f30: 0x403e000041f00000 flt: 30                dbl: 30.000003930181265
      f31: 0x403f000041f80000 flt: 31                dbl: 31.00000393204391
      (gdb) p $f30
      $1 = {f32 = 30, f64 = 30.000003930181265,
            i32 = 1106247680, i64 = 4629137468089696256}
      (gdb) p $f31
      $2 = {f32 = 31, f64 = 31.00000393204391,
            i32 = 1106771968, i64 = 4629418943066931200}

    gdb/ChangeLog:

    	* mips-tdep.c (mips_register_reggroup_p): Match FP registers with
    	mips_float_register_p rather than TYPE_CODE_FLT.
    	(struct mips_reg_part, mips_get_fp_single_location,
    	mips_get_fp_double_location, mips_get_fp_multi_location,
    	mips_regcache_raw_read_parts, mips_regcache_raw_write_parts,
    	mips_fp32_type, mips_fp64_type, mips_fp_type): New definitions.
    	(mips_pseudo_register_read, mips_pseudo_register_write,
    	mips_convert_register_float_case_p, mips_register_to_value,
    	mips_value_to_register, mips_register_type,
    	mips_pseudo_register_type, mips_read_fp_register_single,
    	mips_read_fp_register_double, mips_print_fp_register): Convert to
    	handle new cooked FP format.
    	(mips_gdbarch_init): Initialise tdep->fp32_type and
    	tdep->fp64_type.
    	mips-tdep.h (struct gdbarch_tdep): Add fp32_type and fp64_type.
---
 gdb/mips-tdep.c | 537 ++++++++++++++++++++++++++++++++++++++++++++------------
 gdb/mips-tdep.h |   2 +
 2 files changed, 430 insertions(+), 109 deletions(-)
diff mbox

Patch

diff --git a/gdb/mips-tdep.c b/gdb/mips-tdep.c
index 47ddef4..0024edf 100644
--- a/gdb/mips-tdep.c
+++ b/gdb/mips-tdep.c
@@ -789,6 +789,185 @@  mips_tdesc_register_reggroup_p (struct gdbarch *gdbarch, int regnum,
   return mips_register_reggroup_p (gdbarch, regnum, reggroup);
 }
 
+/* Describes a part of a raw register, used for constructing cooked registers.
+   */
+
+struct mips_reg_part
+{
+  unsigned int regnum;
+  unsigned char offset;
+  unsigned char size;
+};
+
+/* Get the raw register containing a single precision float.
+   Returns the number of parts (maximum 1) written through loc.	 */
+
+static unsigned int
+mips_get_fp_single_location (struct gdbarch *gdbarch,
+			     unsigned int idx,
+			     struct mips_reg_part *loc)
+{
+  int raw_num = mips_regnum (gdbarch)->fp0;
+  int raw_len = register_size (gdbarch, raw_num);
+  enum mips_fpu_mode fp_mode = gdbarch_tdep (gdbarch)->fp_mode;
+  int big_endian;
+
+  /* Only even doubles provided, in pairs of 32-bit registers.  */
+  if (raw_len == 4)
+    {
+      /* In 64-bit FP mode, odd singles don't alias even doubles.  */
+      if (fp_mode == MIPS_FPU_64 && idx & 1)
+	return 0;
+
+      loc->regnum = raw_num + idx;
+      loc->offset = 0;
+      loc->size = 4;
+      return 1;
+    }
+
+  if (raw_len != 8)
+    return 0;
+
+  /* All doubles provided.  */
+  big_endian = (gdbarch_byte_order (gdbarch) == BFD_ENDIAN_BIG);
+
+  loc->size = 4;
+  switch (fp_mode)
+    {
+    case MIPS_FPU_32:
+      loc->regnum = raw_num + (idx & ~1);
+      loc->offset = 4 * (big_endian ^ (idx & 1));
+      return 1;
+    case MIPS_FPU_64:
+      loc->regnum = raw_num + idx;
+      loc->offset = 4 * big_endian;
+      return 1;
+    default:
+      return 0;
+    }
+}
+
+/* Get the raw register part(s) containing a double precision float.
+   Returns the number of parts (maximum 2) written through loc.	 */
+
+static unsigned int
+mips_get_fp_double_location (struct gdbarch *gdbarch,
+			     unsigned int idx,
+			     struct mips_reg_part *loc)
+{
+  int raw_num = mips_regnum (gdbarch)->fp0;
+  int raw_len = register_size (gdbarch, raw_num);
+  enum mips_fpu_mode fp_mode = gdbarch_tdep (gdbarch)->fp_mode;
+  int big_endian = (gdbarch_byte_order (gdbarch) == BFD_ENDIAN_BIG);
+
+  /* Only even doubles provided, in pairs of 32-bit registers.  */
+  if (raw_len == 4)
+    {
+      /* No odd doubles.  */
+      if (idx & 1)
+	return 0;
+
+      /* Even register contains even single, least significant end of double */
+      loc[big_endian].regnum = raw_num + idx;
+      loc[big_endian].offset = 0;
+      loc[big_endian].size = 4;
+      loc[!big_endian].regnum = raw_num + idx + 1;
+      loc[!big_endian].offset = 0;
+      loc[!big_endian].size = 4;
+      return 2;
+    }
+
+  if (raw_len != 8)
+    return 0;
+
+  /* FPU32 doesn't have odd doubles */
+  if (fp_mode == MIPS_FPU_32 && idx & 1)
+    return 0;
+
+  /* All doubles provided.  */
+  loc->regnum = raw_num + idx;
+  loc->offset = 0;
+  loc->size = 8;
+  return 1;
+}
+
+/* Get the raw register part(s) composing a cooked float register.
+   Returns the number of parts (maximum 2) written through loc.	 */
+
+static unsigned int
+mips_get_fp_multi_location (struct gdbarch *gdbarch,
+			    unsigned int idx,
+			    unsigned int cooked_len,
+			    struct mips_reg_part *loc)
+{
+  unsigned int parts, total_parts = 0;
+
+  /* The cooked formats supported are:
+     fp32 (len=4):  just a single.
+     fp64 (len=8):  double (with aliased single).  */
+  if (cooked_len > 8 || cooked_len < 4 || cooked_len & 0x3)
+    internal_error (__FILE__, __LINE__, _("bad cooked register size"));
+
+  /* Formats containing a distinct double.  */
+  if (cooked_len & 8)
+    {
+      parts = mips_get_fp_double_location (gdbarch, idx, loc);
+      if (!parts)
+	return 0;
+      total_parts += parts;
+      loc += parts;
+    }
+
+  /* Formats containing a distinct single.  */
+  if (cooked_len & 4)
+    {
+      parts = mips_get_fp_single_location (gdbarch, idx, loc);
+      if (!parts)
+	return 0;
+      total_parts += parts;
+    }
+
+  return total_parts;
+}
+
+/* Read multiple register parts from the register cache into a buffer.	*/
+
+static enum register_status
+mips_regcache_raw_read_parts (struct regcache *regcache,
+			      struct mips_reg_part *loc,
+			      unsigned int parts,
+			      gdb_byte **buf)
+{
+  enum register_status ret = REG_UNAVAILABLE;
+
+  for (; parts; --parts, ++loc)
+    {
+      ret = regcache_raw_read_part (regcache, loc->regnum, loc->offset,
+				    loc->size, *buf);
+      if (ret != REG_VALID)
+	return ret;
+      *buf += loc->size;
+    }
+
+  return ret;
+}
+
+/* Write multiple register parts of the register cache from a buffer.  */
+
+static void
+mips_regcache_raw_write_parts (struct regcache *regcache,
+			       struct mips_reg_part *loc,
+			       unsigned int parts,
+			       const gdb_byte **buf)
+{
+  for (; parts; --parts, ++loc)
+    {
+      regcache_raw_write_part (regcache, loc->regnum, loc->offset, loc->size,
+			       *buf);
+      *buf += loc->size;
+    }
+}
+
 /* Map the symbol table registers which live in the range [1 *
    gdbarch_num_regs .. 2 * gdbarch_num_regs) back onto the corresponding raw
    registers.  Take care of alignment and size problems.  */
@@ -798,12 +977,27 @@  mips_pseudo_register_read (struct gdbarch *gdbarch, struct regcache *regcache,
 			   int cookednum, gdb_byte *buf)
 {
   int rawnum = cookednum % gdbarch_num_regs (gdbarch);
+  int fpnum;
+  int raw_len, cooked_len;
+
   gdb_assert (cookednum >= gdbarch_num_regs (gdbarch)
 	      && cookednum < 2 * gdbarch_num_regs (gdbarch));
-  if (register_size (gdbarch, rawnum) == register_size (gdbarch, cookednum))
+
+  raw_len = register_size (gdbarch, rawnum);
+  cooked_len = register_size (gdbarch, cookednum);
+
+  if (mips_float_register_p (gdbarch, rawnum))
+    {
+      struct mips_reg_part loc[2];
+      unsigned int parts;
+
+      fpnum = rawnum - mips_regnum (gdbarch)->fp0;
+      parts = mips_get_fp_multi_location (gdbarch, fpnum, cooked_len, loc);
+      return mips_regcache_raw_read_parts (regcache, loc, parts, &buf);
+    }
+  else if (raw_len == cooked_len)
     return regcache_raw_read (regcache, rawnum, buf);
-  else if (register_size (gdbarch, rawnum) >
-	   register_size (gdbarch, cookednum))
+  else if (raw_len > cooked_len)
     {
       if (gdbarch_tdep (gdbarch)->mips64_transfers_32bit_regs_p)
 	return regcache_raw_read_part (regcache, rawnum, 0, 4, buf);
@@ -829,12 +1023,27 @@  mips_pseudo_register_write (struct gdbarch *gdbarch,
 			    const gdb_byte *buf)
 {
   int rawnum = cookednum % gdbarch_num_regs (gdbarch);
+  int fpnum;
+  int raw_len, cooked_len;
+
   gdb_assert (cookednum >= gdbarch_num_regs (gdbarch)
 	      && cookednum < 2 * gdbarch_num_regs (gdbarch));
-  if (register_size (gdbarch, rawnum) == register_size (gdbarch, cookednum))
+
+  raw_len = register_size (gdbarch, rawnum);
+  cooked_len = register_size (gdbarch, cookednum);
+
+  if (mips_float_register_p (gdbarch, rawnum))
+    {
+      struct mips_reg_part loc[2];
+      unsigned int parts;
+
+      fpnum = rawnum - mips_regnum (gdbarch)->fp0;
+      parts = mips_get_fp_multi_location (gdbarch, fpnum, cooked_len, loc);
+      mips_regcache_raw_write_parts (regcache, loc, parts, &buf);
+    }
+  else if (raw_len == cooked_len)
     regcache_raw_write (regcache, rawnum, buf);
-  else if (register_size (gdbarch, rawnum) >
-	   register_size (gdbarch, cookednum))
+  else if (raw_len > cooked_len)
     {
       if (gdbarch_tdep (gdbarch)->mips64_transfers_32bit_regs_p)
 	regcache_raw_write_part (regcache, rawnum, 0, 4, buf);
@@ -927,18 +1136,17 @@  set_mips64_transfers_32bit_regs (char *args, int from_tty,
 
 /* Convert to/from a register and the corresponding memory value.  */
 
-/* This predicate tests for the case of an 8 byte floating point
-   value that is being transferred to or from a pair of floating point
-   registers each of which are (or are considered to be) only 4 bytes
-   wide.  */
+/* This predicate tests for the case of a 4 byte floating point
+   value that is being transferred to or from a floating point
+   register which is 8 bytes wide.  */
+
 static int
 mips_convert_register_float_case_p (struct gdbarch *gdbarch, int regnum,
 				    struct type *type)
 {
-  return (gdbarch_byte_order (gdbarch) == BFD_ENDIAN_BIG
-	  && register_size (gdbarch, regnum) == 4
+  return (register_size (gdbarch, regnum) == 8
 	  && mips_float_register_p (gdbarch, regnum)
-	  && TYPE_CODE (type) == TYPE_CODE_FLT && TYPE_LENGTH (type) == 8);
+	  && TYPE_CODE (type) == TYPE_CODE_FLT && TYPE_LENGTH (type) == 4);
 }
 
 /* This predicate tests for the case of a value of less than 8
@@ -972,16 +1180,19 @@  mips_register_to_value (struct frame_info *frame, int regnum,
 
   if (mips_convert_register_float_case_p (gdbarch, regnum, type))
     {
-      get_frame_register (frame, regnum + 0, to + 4);
-      get_frame_register (frame, regnum + 1, to + 0);
-
-      if (!get_frame_register_bytes (frame, regnum + 0, 0, 4, to + 4,
-				     optimizedp, unavailablep))
-	return 0;
-
-      if (!get_frame_register_bytes (frame, regnum + 1, 0, 4, to + 0,
-				     optimizedp, unavailablep))
-	return 0;
+      /* single comes from low half of 64-bit register */
+      if (gdbarch_byte_order (gdbarch) == BFD_ENDIAN_BIG)
+	{
+	  if (!get_frame_register_bytes (frame, regnum, 4, 4, to,
+					 optimizedp, unavailablep))
+	    return 0;
+	}
+      else
+	{
+	  if (!get_frame_register_bytes (frame, regnum, 0, 4, to,
+					 optimizedp, unavailablep))
+	    return 0;
+	}
       *optimizedp = *unavailablep = 0;
       return 1;
     }
@@ -1024,6 +1235,101 @@  mips_config5_type (struct gdbarch *gdbarch)
   return tdep->config5_type;
 }
 
+/* Get 32-bit only floating point type, which can be interpreted as either a
+   single precision float or a 32-bit signed integer.
+   This is used for odd fp registers when FR=0. In this case there are no odd
+   doubles.  */
+
+static struct type *
+mips_fp32_type (struct gdbarch *gdbarch)
+{
+  struct gdbarch_tdep *tdep = gdbarch_tdep (gdbarch);
+
+  if (tdep->fp32_type == NULL)
+    {
+      const struct builtin_type *bt = builtin_type (gdbarch);
+      struct type *t;
+
+      /* The type we're building is this:
+      union __gdb_builtin_mips_fp32 {
+	  float f32;
+	  int32_t i32;
+      }; */
+
+      t = arch_composite_type (gdbarch, "__gdb_builtin_type_mips_fp32",
+			       TYPE_CODE_UNION);
+      append_composite_type_field (t, "f32", bt->builtin_float);
+      append_composite_type_field (t, "i32", bt->builtin_int32);
+
+      TYPE_NAME (t) = "fp32";
+      tdep->fp32_type = t;
+    }
+
+  return tdep->fp32_type;
+}
+
+/* Get general floating point type, which can be interpreted as either a
+   single or double precision float, or a 32-bit or 64-bit signed integer.
+   This is used for even fp registers when FR=0 (doubles are constructed from
+   even/odd pairs of fp registers so only even registers can be interpreted as
+   double precision flaots) and for all fp registers when FR=1.  */
+
+static struct type *
+mips_fp64_type (struct gdbarch *gdbarch)
+{
+  struct gdbarch_tdep *tdep = gdbarch_tdep (gdbarch);
+
+  if (tdep->fp64_type == NULL)
+    {
+      const struct builtin_type *bt = builtin_type (gdbarch);
+      int big_endian = (gdbarch_byte_order (gdbarch) == BFD_ENDIAN_BIG);
+      struct type *t;
+      struct field *f;
+
+      /* The type we're building is roughly this (little endian):
+
+      union __gdb_builtin_mips_fp64 {
+	  float  f32;
+	  double f64;
+	  int32  i32;
+	  int64  i64;
+      }; */
+
+      t = arch_composite_type (gdbarch, "__gdb_builtin_type_mips_fp64",
+			       TYPE_CODE_UNION);
+      f = append_composite_type_field_raw (t, "f32", bt->builtin_float);
+      SET_FIELD_BITPOS (*f, 32*big_endian);
+      f = append_composite_type_field_raw (t, "f64", bt->builtin_double);
+      SET_FIELD_BITPOS (*f, 0);
+      f = append_composite_type_field_raw (t, "i32", bt->builtin_int32);
+      SET_FIELD_BITPOS (*f, 32*big_endian);
+      f = append_composite_type_field_raw (t, "i64", bt->builtin_int64);
+      SET_FIELD_BITPOS (*f, 0);
+
+      TYPE_LENGTH (t) = 8;
+      TYPE_NAME (t) = "fp64";
+      tdep->fp64_type = t;
+    }
+
+  return tdep->fp64_type;
+}
+
+/* Get the floating point type for an arbitrary FP register. This returns the
+   appropriate type depending on the possible types and overlaps of the
+   register.  */
+
+static struct type *
+mips_fp_type (struct gdbarch *gdbarch, int fpnum)
+{
+  if ((fpnum & 1) == 0 || mips_float_regsize (gdbarch) == 8)
+    /* Even singles and doubles always overlap, as do odd singles and
+       doubles when FR=1.  */
+    return mips_fp64_type (gdbarch);
+  else
+    /* 32-bit odd singles (there are no odd doubles).  */
+    return mips_fp32_type (gdbarch);
+}
+
 static void
 mips_value_to_register (struct frame_info *frame, int regnum,
 			struct type *type, const gdb_byte *from)
@@ -1032,8 +1338,11 @@  mips_value_to_register (struct frame_info *frame, int regnum,
 
   if (mips_convert_register_float_case_p (gdbarch, regnum, type))
     {
-      put_frame_register (frame, regnum + 0, from + 4);
-      put_frame_register (frame, regnum + 1, from + 0);
+      /* single goes in low half of 64-bit register */
+      if (gdbarch_byte_order (gdbarch) == BFD_ENDIAN_BIG)
+	put_frame_register_bytes (frame, regnum, 4, 4, from);
+      else
+	put_frame_register_bytes (frame, regnum, 0, 4, from);
     }
   else if (mips_convert_register_gpreg_case_p (gdbarch, regnum, type))
     {
@@ -1105,9 +1414,7 @@  mips_register_type (struct gdbarch *gdbarch, int regnum)
          either 64-bit or 32-bit via the CP0 Status register's FR bit.
          Use the current setting for cooked registers.  */
       if (mips_float_register_p (gdbarch, regnum))
-	return (mips_float_regsize (gdbarch) == 4
-		? builtin_type (gdbarch)->builtin_float
-		: builtin_type (gdbarch)->builtin_double);
+	return mips_fp_type (gdbarch, rawnum - mips_regnum (gdbarch)->fp0);
       else if (rawnum == mips_regnum (gdbarch)->fp_control_status
 	  || rawnum == mips_regnum (gdbarch)->fp_implementation_revision)
 	return builtin_type (gdbarch)->builtin_int32;
@@ -1145,7 +1452,7 @@  mips_pseudo_register_type (struct gdbarch *gdbarch, int regnum)
 {
   const int num_regs = gdbarch_num_regs (gdbarch);
   int rawnum = regnum % num_regs;
-  struct type *rawtype;
+  struct type *rawtype, *fp_rawtype;
 
   gdb_assert (regnum >= num_regs && regnum < 2 * num_regs);
 
@@ -1159,7 +1466,7 @@  mips_pseudo_register_type (struct gdbarch *gdbarch, int regnum)
        not try to convert between FPU layouts.  A target description is
        expected to have taken the CP0 Status register's FR bit into account
        as necessary, this has been already verified in mips_gdbarch_init.  */
-    return rawtype;
+    return mips_fp_type (gdbarch, rawnum - mips_regnum (gdbarch)->fp0);
 
   /* Use pointer types for registers if we can.  For n32 we can not,
      since we do not have a 64-bit pointer type.  */
@@ -6266,79 +6573,60 @@  mips_o64_return_value (struct gdbarch *gdbarch, struct value *function,
    and below).  */
 
 /* Copy a 32-bit single-precision value from the current frame
-   into rare_buffer.  */
+   into rare_buffer. This is done by reading the pseudo register and extracting
+   the relevant part so as not to duplicate code.
+   Returns 0 on failure. */
 
-static void
+static int
 mips_read_fp_register_single (struct frame_info *frame, int regno,
 			      gdb_byte *rare_buffer)
 {
   struct gdbarch *gdbarch = get_frame_arch (frame);
-  int fpsize = register_size (gdbarch, regno);
-  gdb_byte *raw_buffer = alloca (fpsize);
+  int cooked_size = register_size (gdbarch, regno);
+  gdb_byte *raw_buffer = alloca (cooked_size);
+  int big_endian = (gdbarch_byte_order (gdbarch) == BFD_ENDIAN_BIG);
 
-  if (!deprecated_frame_register_read (frame, regno, raw_buffer))
-    error (_("can't read register %d (%s)"),
-	   regno, gdbarch_register_name (gdbarch, regno));
-  if (fpsize == 8)
-    {
-      /* We have a 64-bit value for this register.  Find the low-order
-         32 bits.  */
-      int offset;
+  if (cooked_size < 4)
+    return 0;
 
-      if (gdbarch_byte_order (gdbarch) == BFD_ENDIAN_BIG)
-	offset = 4;
-      else
-	offset = 0;
+  if (cooked_size == 4)
+    /* FR=0 odd */
+    return deprecated_frame_register_read (frame, regno, rare_buffer);
 
-      memcpy (rare_buffer, raw_buffer + offset, 4);
-    }
+  if (!deprecated_frame_register_read (frame, regno, raw_buffer))
+    return 0;
+
+  if (cooked_size == 8)
+    /* FR=1
+       Single is overlapping double. */
+    memcpy(rare_buffer, raw_buffer + 4*big_endian, 4);
   else
-    {
-      memcpy (rare_buffer, raw_buffer, 4);
-    }
+    return 0;
+  return 1;
 }
 
 /* Copy a 64-bit double-precision value from the current frame into
-   rare_buffer.  This may include getting half of it from the next
-   register.  */
+   rare_buffer. This is done by reading the pseudo register and extracting the
+   relevant part so as not to duplicate code.
+   Returns 0 on failure. */
 
-static void
+static int
 mips_read_fp_register_double (struct frame_info *frame, int regno,
 			      gdb_byte *rare_buffer)
 {
   struct gdbarch *gdbarch = get_frame_arch (frame);
-  int fpsize = register_size (gdbarch, regno);
+  int cooked_size = register_size (gdbarch, regno);
+  gdb_byte *raw_buffer = alloca (cooked_size);
 
-  if (fpsize == 8)
-    {
-      /* We have a 64-bit value for this register, and we should use
-         all 64 bits.  */
-      if (!deprecated_frame_register_read (frame, regno, rare_buffer))
-	error (_("can't read register %d (%s)"),
-	       regno, gdbarch_register_name (gdbarch, regno));
-    }
-  else
-    {
-      int rawnum = regno % gdbarch_num_regs (gdbarch);
+  if (cooked_size < 8)
+    /* FR=0 odd */
+    return 0;
 
-      if ((rawnum - mips_regnum (gdbarch)->fp0) & 1)
-	internal_error (__FILE__, __LINE__,
-			_("mips_read_fp_register_double: bad access to "
-			"odd-numbered FP register"));
+  if (!deprecated_frame_register_read (frame, regno, raw_buffer))
+    return 0;
 
-      /* mips_read_fp_register_single will find the correct 32 bits from
-         each register.  */
-      if (gdbarch_byte_order (gdbarch) == BFD_ENDIAN_BIG)
-	{
-	  mips_read_fp_register_single (frame, regno, rare_buffer + 4);
-	  mips_read_fp_register_single (frame, regno + 1, rare_buffer);
-	}
-      else
-	{
-	  mips_read_fp_register_single (frame, regno, rare_buffer);
-	  mips_read_fp_register_single (frame, regno + 1, rare_buffer + 4);
-	}
-    }
+  memcpy(rare_buffer, raw_buffer, 8);
+  return 1;
 }
 
 static void
@@ -6349,7 +6637,7 @@  mips_print_fp_register (struct ui_file *file, struct frame_info *frame,
   int fpsize = register_size (gdbarch, regnum);
   gdb_byte *raw_buffer;
   double doub, flt1;	/* Doubles extracted from raw hex data.  */
-  int inv1, inv2;
+  int res1, res2, inv1, inv2;
 
   raw_buffer = alloca (2 * fpsize);
 
@@ -6364,29 +6652,39 @@  mips_print_fp_register (struct ui_file *file, struct frame_info *frame,
 
       /* 4-byte registers: Print hex and floating.  Also print even
          numbered registers as doubles.  */
-      mips_read_fp_register_single (frame, regnum, raw_buffer);
-      flt1 = unpack_double (builtin_type (gdbarch)->builtin_float,
-			    raw_buffer, &inv1);
+      res1 = mips_read_fp_register_single (frame, regnum, raw_buffer);
+      if (res1)
+	{
+	  flt1 = unpack_double (builtin_type (gdbarch)->builtin_float,
+				raw_buffer, &inv1);
 
-      get_formatted_print_options (&opts, 'x');
-      print_scalar_formatted (raw_buffer,
-			      builtin_type (gdbarch)->builtin_uint32,
-			      &opts, 'w', file);
+	  get_formatted_print_options (&opts, 'x');
+	  print_scalar_formatted (raw_buffer,
+				  builtin_type (gdbarch)->builtin_uint32,
+				  &opts, 'w', file);
+	}
+      else
+	fprintf_filtered (file, "0x????????");
 
       fprintf_filtered (file, " flt: ");
-      if (inv1)
+      if (!res1)
+	fprintf_filtered (file, " <unavailable>   ");
+      else if (inv1)
 	fprintf_filtered (file, " <invalid float> ");
       else
 	fprintf_filtered (file, "%-17.9g", flt1);
 
       if ((regnum - gdbarch_num_regs (gdbarch)) % 2 == 0)
 	{
-	  mips_read_fp_register_double (frame, regnum, raw_buffer);
-	  doub = unpack_double (builtin_type (gdbarch)->builtin_double,
-				raw_buffer, &inv2);
+	  res2 = mips_read_fp_register_double (frame, regnum, raw_buffer);
+	  if (res2)
+	      doub = unpack_double (builtin_type (gdbarch)->builtin_double,
+				    raw_buffer, &inv2);
 
 	  fprintf_filtered (file, " dbl: ");
-	  if (inv2)
+	  if (!res2)
+	    fprintf_filtered (file, "<unavailable>");
+	  else if (inv2)
 	    fprintf_filtered (file, "<invalid double>");
 	  else
 	    fprintf_filtered (file, "%-24.17g", doub);
@@ -6395,29 +6693,48 @@  mips_print_fp_register (struct ui_file *file, struct frame_info *frame,
   else
     {
       struct value_print_options opts;
+      int no_odd;
+
+      /* if top half isn't provided we can't access odd floats or doubles */
+      if ((regnum - gdbarch_num_regs (gdbarch)) & 1 &&
+	  register_size (gdbarch, regnum % gdbarch_num_regs (gdbarch)) < 8)
+	{
+	  fprintf_filtered (file, "<unavailable>");
+	  return;
+	}
 
       /* Eight byte registers: print each one as hex, float and double.  */
-      mips_read_fp_register_single (frame, regnum, raw_buffer);
-      flt1 = unpack_double (builtin_type (gdbarch)->builtin_float,
-			    raw_buffer, &inv1);
+      res1 = mips_read_fp_register_single (frame, regnum, raw_buffer);
+      if (res1)
+	flt1 = unpack_double (builtin_type (gdbarch)->builtin_float,
+			      raw_buffer, &inv1);
 
-      mips_read_fp_register_double (frame, regnum, raw_buffer);
-      doub = unpack_double (builtin_type (gdbarch)->builtin_double,
-			    raw_buffer, &inv2);
+      res2 = mips_read_fp_register_double (frame, regnum, raw_buffer);
+      if (res2)
+	{
+	  doub = unpack_double (builtin_type (gdbarch)->builtin_double,
+				raw_buffer, &inv2);
 
-      get_formatted_print_options (&opts, 'x');
-      print_scalar_formatted (raw_buffer,
-			      builtin_type (gdbarch)->builtin_uint64,
-			      &opts, 'g', file);
+	  get_formatted_print_options (&opts, 'x');
+	  print_scalar_formatted (raw_buffer,
+				  builtin_type (gdbarch)->builtin_uint64,
+				  &opts, 'g', file);
+	}
+      else
+	fprintf_filtered (file, "0x????????????????");
 
       fprintf_filtered (file, " flt: ");
-      if (inv1)
-	fprintf_filtered (file, "<invalid float>");
+      if (!res1)
+	fprintf_filtered (file, " <unavailable>  ");
+      else if (inv1)
+	fprintf_filtered (file, " <invalid float>");
       else
 	fprintf_filtered (file, "%-17.9g", flt1);
 
       fprintf_filtered (file, " dbl: ");
-      if (inv2)
+      if (!res2)
+	fprintf_filtered (file, "<unavailable>   ");
+      else if (inv2)
 	fprintf_filtered (file, "<invalid double>");
       else
 	fprintf_filtered (file, "%-24.17g", doub);
@@ -8716,6 +9033,8 @@  mips_gdbarch_init (struct gdbarch_info info, struct gdbarch_list *arches)
   tdep->fp_mode = ((struct gdbarch_tdep_info*)(info.tdep_info))->fp_mode;
   tdep->fp_register_mode_fixed_p = 0;
   tdep->config5_type = NULL;
+  tdep->fp32_type = NULL;
+  tdep->fp64_type = NULL;
 
   if (info.target_desc)
     {
diff --git a/gdb/mips-tdep.h b/gdb/mips-tdep.h
index b6f99f2..11353be 100644
--- a/gdb/mips-tdep.h
+++ b/gdb/mips-tdep.h
@@ -130,6 +130,8 @@  struct gdbarch_tdep
 
   /* ISA-specific data types.  */
   struct type *config5_type;
+  struct type *fp32_type;
+  struct type *fp64_type;
 
   /* Return the expected next PC if FRAME is stopped at a syscall
      instruction.  */