From 66a44c41da0af748d6d9199349fda327fe9eccec Mon Sep 17 00:00:00 2001
From: Matthew Fortune <matthew.fortune@imgtec.com>
Date: Thu, 29 May 2014 13:33:06 +0100
Subject: [PATCH] Implement O32 FPXX

2014-07-31  Matthew Fortune  <matthew.fortune@imgtec.com>

gcc/
	* common/config/mips/mips-common.c (mips_handle_option): Ensure
	that -mfp32, -mfp64 disable -mfpxx and -mfpxx disables -mfp64.
	* config.gcc (--with-fp-32): New option.
	(--with-odd-spreg-32): Likewise.
	* config.in (HAVE_AS_MODULE): New config define.
	* config/mips/mips-protos.h
	(mips_secondary_memory_needed): New prototype.
	(mips_hard_regno_caller_save_mode): Likewise.
	* config/mips/mips.c (mips_get_reg_raw_mode): New static prototype.
	(mips_get_arg_info): Assert that V2SFmode is only handled specially
	with TARGET_PAIRED_SINGLE_FLOAT.
	(mips_return_mode_in_fpr_p): Likewise.
	(mips16_call_stub_mode_suffix): Likewise.
	(mips_get_reg_raw_mode): New static function.
	(mips_return_fpr_pair): O32 return values span two registers.
	(mips16_build_call_stub): Likewise.
	(mips_function_value_regno_p): Support both FP return registers.
	(mips_output_64bit_xfer): Use mthc1 whenever TARGET_HAS_MXHC1.  Add
	specific cases for TARGET_FPXX to move via memory.
	(mips_dwarf_register_span): For TARGET_FPXX pretend that modes larger
	than UNITS_PER_FPREG 'span' one register.
	(mips_dwarf_frame_reg_mode): New static function.
	(mips_file_start): Switch to using .module instead of .gnu_attribute.
	No longer support FP ABI 4 (-mips32r2 -mfp64), replace with FP ABI 6.
	Add FP ABI 5 (-mfpxx) and FP ABI 7 (-mfp64 -mno-odd-spreg).
	(mips_save_reg, mips_restore_reg): Always represent DFmode frame
	slots with two CFI directives even for O32 FP64.
	(mips_for_each_saved_gpr_and_fpr): Account for fixed_regs when
	saving/restoring callee-saved registers.
	(mips_hard_regno_mode_ok_p): Implement O32 FP64A extension.
	(mips_secondary_memory_needed): New function.
	(mips_option_override): ABI check for TARGET_FLOATXX.  Disable
	odd-numbered single-precision registers	when using TARGET_FLOATXX.
	Implement -modd-spreg and defaults.
	(mips_conditional_register_usage): Redefine O32 FP64 to match O32 FP32
	callee-saved behaviour.
	(mips_hard_regno_caller_save_mode): Implement.
	(TARGET_GET_RAW_RESULT_MODE): Define target hook.
	(TARGET_GET_RAW_ARG_MODE): Define target hook.
	(TARGET_DWARF_FRAME_REG_MODE): Define target hook.
	* config/mips/mips.h (TARGET_FLOAT32): New macro.
	(TARGET_CPU_CPP_BUILTINS): TARGET_FPXX is __mips_fpr==0. Add
	_MIPS_SPFPSET builtin define.
	(MIPS_FPXX_OPTION_SPEC): New macro.
	(OPTION_DEFAULT_SPECS): Pass through --with-fp-32=* to -mfp and
	--with-odd-spreg-32=* to -m[no-]odd-spreg.
	(ISA_HAS_ODD_SPREG): New macro.
	(ISA_HAS_MXHC1): True for anything other than -mfp32.
	(ASM_SPEC): Pass through mfpxx, mfp64, -mno-odd-spreg and -modd-spreg.
	(MIN_FPRS_PER_FMT): Redefine in terms of TARGET_ODD_SPREG.
	(HARD_REGNO_CALLER_SAVE_MODE): Define.  Implement O32 FPXX extension
	(HARD_REGNO_CALL_PART_CLOBBERED): Likewise.
	(SECONDARY_MEMORY_NEEDED): Likewise.
	(FUNCTION_ARG_REGNO_P): Update for O32 FPXX and FP64 extensions.
	* config/mips/mips.md (define_attr enabled): Implement O32 FPXX and
	FP64A ABI extensions.
	(move_doubleword_fpr<mode>): Use ISA_HAS_MXHC1 instead of
	TARGET_FLOAT64.
	* config/mips/mips.opt (mfpxx): New target option.
	(modd-spreg): Likewise.
	* config/mips/mti-elf.h (DRIVER_SELF_SPECS): Infer FP ABI from arch.
	* config/mips/mti-linux.h (DRIVER_SELF_SPECS): Likewise and remove
	fp64 sysroot.
	* config/mips/t-mti-elf: Remove fp64 multilib.
	* config/mips/t-mti-linux: Likewise.
	* configure.ac: Detect .module support.
	* configure: Regenerate.
	* doc/invoke.texi: Document -mfpxx, -modd-spreg, -mno-odd-spreg option.

gcc/testsuite/
	* gcc.target/mips/args-1.c: Handle __mips_fpr == 0.
	* gcc.target/mips/call-clobbered-1.c: New.
	* gcc.target/mips/call-clobbered-2.c: New.
	* gcc.target/mips/call-clobbered-3.c: New.
	* gcc.target/mips/call-clobbered-4.c: New.
	* gcc.target/mips/call-clobbered-5.c: New.
	* gcc.target/mips/call-saved-4.c: New.
	* gcc.target/mips/call-saved-5.c: New.
	* gcc.target/mips/call-saved-6.c: New.
	* gcc.target/mips/mips.exp: Support -mfpxx, -ffixed-f*,
	and -m[no-]odd-spreg.  Use _MIPS_SPFPSET to determine default
	odd-spreg option.  Account for -modd-spreg in minimum arch code.
	* gcc.target/mips/movdf-1.c: New.
	* gcc.target/mips/movdf-2.c: New.
	* gcc.target/mips/movdf-3.c: New.
	* gcc.target/mips/oddspreg-1.c: New.
	* gcc.target/mips/oddspreg-2.c: New.
	* gcc.target/mips/oddspreg-3.c: New.
	* gcc.target/mips/oddspreg-4.c: New.
	* gcc.target/mips/oddspreg-5.c: New.
	* gcc.target/mips/oddspreg-6.c: New.

libgcc/
	* config/mips/mips16.S: Set .module when supported.  Update O32
	FP64 calling convention and use for FPXX when possible.  Add FPXX
	calling convention fallback case.
---
 gcc/common/config/mips/mips-common.c             |   9 +
 gcc/config.gcc                                   |  30 ++-
 gcc/config.in                                    |   6 +
 gcc/config/mips/mips-protos.h                    |   6 +-
 gcc/config/mips/mips.c                           | 234 +++++++++++++++++++++--
 gcc/config/mips/mips.h                           |  68 ++++++-
 gcc/config/mips/mips.md                          |  24 ++-
 gcc/config/mips/mips.opt                         |   8 +
 gcc/config/mips/mti-elf.h                        |   3 +
 gcc/config/mips/mti-linux.h                      |   3 +
 gcc/configure                                    |  35 ++++
 gcc/configure.ac                                 |  11 ++
 gcc/doc/invoke.texi                              |  32 +++-
 gcc/testsuite/gcc.target/mips/args-1.c           |   2 +-
 gcc/testsuite/gcc.target/mips/call-clobbered-1.c |  21 ++
 gcc/testsuite/gcc.target/mips/call-clobbered-2.c |  21 ++
 gcc/testsuite/gcc.target/mips/call-clobbered-3.c |  23 +++
 gcc/testsuite/gcc.target/mips/call-clobbered-4.c |  23 +++
 gcc/testsuite/gcc.target/mips/call-clobbered-5.c |  21 ++
 gcc/testsuite/gcc.target/mips/call-saved-4.c     |  32 ++++
 gcc/testsuite/gcc.target/mips/call-saved-5.c     |  32 ++++
 gcc/testsuite/gcc.target/mips/call-saved-6.c     |  32 ++++
 gcc/testsuite/gcc.target/mips/mips.exp           |  40 +++-
 gcc/testsuite/gcc.target/mips/movdf-1.c          |  14 ++
 gcc/testsuite/gcc.target/mips/movdf-2.c          |  14 ++
 gcc/testsuite/gcc.target/mips/movdf-3.c          |  13 ++
 gcc/testsuite/gcc.target/mips/oddspreg-1.c       |  13 ++
 gcc/testsuite/gcc.target/mips/oddspreg-2.c       |  10 +
 gcc/testsuite/gcc.target/mips/oddspreg-3.c       |  10 +
 gcc/testsuite/gcc.target/mips/oddspreg-4.c       |  15 ++
 gcc/testsuite/gcc.target/mips/oddspreg-5.c       |  11 ++
 gcc/testsuite/gcc.target/mips/oddspreg-6.c       |  10 +
 libgcc/config/mips/mips16.S                      |  35 +++-
 33 files changed, 811 insertions(+), 50 deletions(-)
 create mode 100644 gcc/testsuite/gcc.target/mips/call-clobbered-1.c
 create mode 100644 gcc/testsuite/gcc.target/mips/call-clobbered-2.c
 create mode 100644 gcc/testsuite/gcc.target/mips/call-clobbered-3.c
 create mode 100644 gcc/testsuite/gcc.target/mips/call-clobbered-4.c
 create mode 100644 gcc/testsuite/gcc.target/mips/call-clobbered-5.c
 create mode 100644 gcc/testsuite/gcc.target/mips/call-saved-4.c
 create mode 100644 gcc/testsuite/gcc.target/mips/call-saved-5.c
 create mode 100644 gcc/testsuite/gcc.target/mips/call-saved-6.c
 create mode 100644 gcc/testsuite/gcc.target/mips/movdf-1.c
 create mode 100644 gcc/testsuite/gcc.target/mips/movdf-2.c
 create mode 100644 gcc/testsuite/gcc.target/mips/movdf-3.c
 create mode 100644 gcc/testsuite/gcc.target/mips/oddspreg-1.c
 create mode 100644 gcc/testsuite/gcc.target/mips/oddspreg-2.c
 create mode 100644 gcc/testsuite/gcc.target/mips/oddspreg-3.c
 create mode 100644 gcc/testsuite/gcc.target/mips/oddspreg-4.c
 create mode 100644 gcc/testsuite/gcc.target/mips/oddspreg-5.c
 create mode 100644 gcc/testsuite/gcc.target/mips/oddspreg-6.c

diff --git a/gcc/common/config/mips/mips-common.c b/gcc/common/config/mips/mips-common.c
index 7dd8d2d..a140d55 100644
--- a/gcc/common/config/mips/mips-common.c
+++ b/gcc/common/config/mips/mips-common.c
@@ -42,6 +42,15 @@ mips_handle_option (struct gcc_options *opts,
       opts->x_mips_cache_flush_func = NULL;
       return true;
 
+    case OPT_mfp32:
+    case OPT_mfp64:
+      opts->x_target_flags &= ~MASK_FLOATXX;
+      return true;
+
+    case OPT_mfpxx:
+      opts->x_target_flags &= ~MASK_FLOAT64;
+      return true;
+
     default:
       return true;
     }
diff --git a/gcc/config.gcc b/gcc/config.gcc
index e1f1532..5a922af 100644
--- a/gcc/config.gcc
+++ b/gcc/config.gcc
@@ -3747,7 +3747,7 @@ case "${target}" in
 		;;
 
 	mips*-*-*)
-		supported_defaults="abi arch arch_32 arch_64 float fpu nan tune tune_32 tune_64 divide llsc mips-plt synci"
+		supported_defaults="abi arch arch_32 arch_64 float fpu nan fp_32 odd_spreg_32 tune tune_32 tune_64 divide llsc mips-plt synci"
 
 		case ${with_float} in
 		"" | soft | hard)
@@ -3779,6 +3779,32 @@ case "${target}" in
 			;;
 		esac
 
+		case ${with_fp_32} in
+		"" | 32 | xx | 64)
+			# OK
+			;;
+		*)
+			echo "Unknown FP mode used in --with-fp-32=$with_fp_32" 1>&2
+			exit 1
+			;;
+		esac
+
+		case ${with_odd_spreg_32} in
+		yes)
+			with_odd_spreg_32="odd-spreg"
+			;;
+		no)
+			with_odd_spreg_32="no-odd-spreg"
+			;;
+		"")
+			# OK
+			;;
+		*)
+			echo "Unknown odd-spreg-32 type used in --with-odd-spreg-32=$with_odd_spreg_32" 1>&2
+			exit 1
+			;;
+		esac
+
 		case ${with_abi} in
 		"" | 32 | o64 | n32 | 64 | eabi)
 			# OK
@@ -4183,7 +4209,7 @@ case ${target} in
 esac
 
 t=
-all_defaults="abi cpu cpu_32 cpu_64 arch arch_32 arch_64 tune tune_32 tune_64 schedule float mode fpu nan divide llsc mips-plt synci tls"
+all_defaults="abi cpu cpu_32 cpu_64 arch arch_32 arch_64 tune tune_32 tune_64 schedule float mode fpu nan fp_32 odd_spreg_32 divide llsc mips-plt synci tls"
 for option in $all_defaults
 do
 	eval "val=\$with_"`echo $option | sed s/-/_/g`
diff --git a/gcc/config.in b/gcc/config.in
index a1bda76..3624792 100644
--- a/gcc/config.in
+++ b/gcc/config.in
@@ -472,6 +472,12 @@
 #endif
 
 
+/* Define if the assembler understands .module. */
+#ifndef USED_FOR_TARGET
+#undef HAVE_AS_MODULE
+#endif
+
+
 /* Define if your assembler supports the -no-mul-bug-abort option. */
 #ifndef USED_FOR_TARGET
 #undef HAVE_AS_NO_MUL_BUG_ABORT_OPTION
diff --git a/gcc/config/mips/mips-protos.h b/gcc/config/mips/mips-protos.h
index 8a517ee..3e0eb1c 100644
--- a/gcc/config/mips/mips-protos.h
+++ b/gcc/config/mips/mips-protos.h
@@ -277,6 +277,8 @@ extern void mips_expand_before_return (void);
 extern void mips_expand_epilogue (bool);
 extern bool mips_can_use_return_insn (void);
 
+extern bool mips_secondary_memory_needed (enum reg_class, enum reg_class,
+					  enum machine_mode);
 extern bool mips_cannot_change_mode_class (enum machine_mode,
 					   enum machine_mode, enum reg_class);
 extern bool mips_dangerous_for_la25_p (rtx);
@@ -285,7 +287,9 @@ extern enum reg_class mips_secondary_reload_class (enum reg_class,
 						   enum machine_mode,
 						   rtx, bool);
 extern int mips_class_max_nregs (enum reg_class, enum machine_mode);
-
+extern enum machine_mode mips_hard_regno_caller_save_mode (unsigned int,
+							   unsigned int,
+							   enum machine_mode);
 extern int mips_adjust_insn_length (rtx, int);
 extern void mips_output_load_label (rtx);
 extern const char *mips_output_conditional_branch (rtx, rtx *, const char *,
diff --git a/gcc/config/mips/mips.c b/gcc/config/mips/mips.c
index bfbf61b..bd28cbd 100644
--- a/gcc/config/mips/mips.c
+++ b/gcc/config/mips/mips.c
@@ -1199,6 +1199,7 @@ static rtx mips_find_pic_call_symbol (rtx, rtx, bool);
 static int mips_register_move_cost (enum machine_mode, reg_class_t,
 				    reg_class_t);
 static unsigned int mips_function_arg_boundary (enum machine_mode, const_tree);
+static enum machine_mode mips_get_reg_raw_mode (int regno);
 
 /* This hash table keeps track of implicit "mips16" and "nomips16" attributes
    for -mflip_mips16.  It maps decl names onto a boolean mode setting.  */
@@ -5136,6 +5137,7 @@ mips_get_arg_info (struct mips_arg_info *info, const CUMULATIVE_ARGS *cum,
       /* Only leading floating-point scalars are passed in
 	 floating-point registers.  We also handle vector floats the same
 	 say, which is OK because they are not covered by the standard ABI.  */
+      gcc_assert (TARGET_PAIRED_SINGLE_FLOAT || mode != V2SFmode);
       info->fpr_p = (!cum->gp_reg_found
 		     && cum->arg_number < 2
 		     && (type == 0
@@ -5151,6 +5153,7 @@ mips_get_arg_info (struct mips_arg_info *info, const CUMULATIVE_ARGS *cum,
       /* Scalar, complex and vector floating-point types are passed in
 	 floating-point registers, as long as this is a named rather
 	 than a variable argument.  */
+      gcc_assert (TARGET_PAIRED_SINGLE_FLOAT || mode != V2SFmode);
       info->fpr_p = (named
 		     && (type == 0 || FLOAT_TYPE_P (type))
 		     && (GET_MODE_CLASS (mode) == MODE_FLOAT
@@ -5434,6 +5437,16 @@ mips_function_arg_boundary (enum machine_mode mode, const_tree type)
   return alignment;
 }
 
+/* Implement TARGET_GET_RAW_RESULT_MODE and TARGET_GET_RAW_ARG_MODE.  */
+
+static enum machine_mode
+mips_get_reg_raw_mode (int regno)
+{
+  if (mips_abi == ABI_32 && !TARGET_FLOAT32 && FP_REG_P (regno))
+    return DFmode;
+  return default_get_reg_raw_mode (regno);
+}
+
 /* Return true if FUNCTION_ARG_PADDING (MODE, TYPE) should return
    upward rather than downward.  In other words, return true if the
    first byte of the stack slot has useful data, false if the last
@@ -5591,6 +5604,7 @@ mips_return_in_msb (const_tree valtype)
 static bool
 mips_return_mode_in_fpr_p (enum machine_mode mode)
 {
+  gcc_assert (TARGET_PAIRED_SINGLE_FLOAT || mode != V2SFmode);
   return ((GET_MODE_CLASS (mode) == MODE_FLOAT
 	   || mode == V2SFmode
 	   || GET_MODE_CLASS (mode) == MODE_COMPLEX_FLOAT)
@@ -5637,7 +5651,7 @@ mips_return_fpr_pair (enum machine_mode mode,
 {
   int inc;
 
-  inc = (TARGET_NEWABI ? 2 : MAX_FPRS_PER_FMT);
+  inc = (TARGET_NEWABI || mips_abi == ABI_32 ? 2 : MAX_FPRS_PER_FMT);
   return gen_rtx_PARALLEL
     (mode,
      gen_rtvec (2,
@@ -5753,19 +5767,26 @@ mips_libcall_value (enum machine_mode mode, const_rtx fun ATTRIBUTE_UNUSED)
 
 /* Implement TARGET_FUNCTION_VALUE_REGNO_P.
 
-   On the MIPS, R2 R3 and F0 F2 are the only register thus used.
-   Currently, R2 and F0 are only implemented here (C has no complex type).  */
+   On the MIPS, R2 R3 and F0 F2 are the only register thus used.  */
 
 static bool
 mips_function_value_regno_p (const unsigned int regno)
 {
   if (regno == GP_RETURN
       || regno == FP_RETURN
+      || regno == FP_RETURN + 2
       || (LONG_DOUBLE_TYPE_SIZE == 128
 	  && FP_RETURN != GP_RETURN
 	  && regno == FP_RETURN + 2))
     return true;
 
+  if ((regno == FP_RETURN + 1
+      || regno == FP_RETURN + 3)
+      && FP_RETURN != GP_RETURN
+      && (mips_abi == ABI_32 && TARGET_FLOAT32)
+      && FP_REG_P (regno))
+    return true;
+
   return false;
 }
 
@@ -6485,7 +6506,10 @@ mips16_call_stub_mode_suffix (enum machine_mode mode)
   else if (mode == DCmode)
     return "dc";
   else if (mode == V2SFmode)
-    return "df";
+    {
+      gcc_assert (TARGET_PAIRED_SINGLE_FLOAT);
+      return "df";
+    }
   else
     gcc_unreachable ();
 }
@@ -6509,13 +6533,27 @@ mips_output_64bit_xfer (char direction, unsigned int gpreg, unsigned int fpreg)
   if (TARGET_64BIT)
     fprintf (asm_out_file, "\tdm%cc1\t%s,%s\n", direction,
  	     reg_names[gpreg], reg_names[fpreg]);
-  else if (TARGET_FLOAT64)
+  else if (ISA_HAS_MXHC1)
     {
       fprintf (asm_out_file, "\tm%cc1\t%s,%s\n", direction,
  	       reg_names[gpreg + TARGET_BIG_ENDIAN], reg_names[fpreg]);
       fprintf (asm_out_file, "\tm%chc1\t%s,%s\n", direction,
  	       reg_names[gpreg + TARGET_LITTLE_ENDIAN], reg_names[fpreg]);
     }
+  else if (TARGET_FLOATXX && direction == 't')
+    {
+      /* Use the argument save area to move via memory.  */
+      fprintf (asm_out_file, "\tsw\t%s,0($sp)\n", reg_names[gpreg]);
+      fprintf (asm_out_file, "\tsw\t%s,4($sp)\n", reg_names[gpreg + 1]);
+      fprintf (asm_out_file, "\tldc1\t%s,0($sp)\n", reg_names[fpreg]);
+    }
+  else if (TARGET_FLOATXX && direction == 'f')
+    {
+      /* Use the argument save area to move via memory.  */
+      fprintf (asm_out_file, "\tsdc1\t%s,0($sp)\n", reg_names[fpreg]);
+      fprintf (asm_out_file, "\tlw\t%s,0($sp)\n", reg_names[gpreg]);
+      fprintf (asm_out_file, "\tlw\t%s,4($sp)\n", reg_names[gpreg + 1]);
+    }
   else
     {
       /* Move the least-significant word.  */
@@ -6921,11 +6959,11 @@ mips16_build_call_stub (rtx retval, rtx *fn_ptr, rtx args_size, int fp_code)
 	    case SCmode:
 	      mips_output_32bit_xfer ('f', GP_RETURN + TARGET_BIG_ENDIAN,
 				      TARGET_BIG_ENDIAN
-				      ? FP_REG_FIRST + MAX_FPRS_PER_FMT
+				      ? FP_REG_FIRST + 2
 				      : FP_REG_FIRST);
 	      mips_output_32bit_xfer ('f', GP_RETURN + TARGET_LITTLE_ENDIAN,
 				      TARGET_LITTLE_ENDIAN
-				      ? FP_REG_FIRST + MAX_FPRS_PER_FMT
+				      ? FP_REG_FIRST + 2
 				      : FP_REG_FIRST);
 	      if (GET_MODE (retval) == SCmode && TARGET_64BIT)
 		{
@@ -6954,10 +6992,12 @@ mips16_build_call_stub (rtx retval, rtx *fn_ptr, rtx args_size, int fp_code)
 
 	    case DCmode:
 	      mips_output_64bit_xfer ('f', GP_RETURN + (8 / UNITS_PER_WORD),
-				      FP_REG_FIRST + MAX_FPRS_PER_FMT);
+				      FP_REG_FIRST + 2);
 	      /* Fall though.  */
  	    case DFmode:
 	    case V2SFmode:
+	      gcc_assert (TARGET_PAIRED_SINGLE_FLOAT
+			  || GET_MODE (retval) != V2SFmode);
 	      mips_output_64bit_xfer ('f', GP_RETURN, FP_REG_FIRST);
 	      break;
 
@@ -8661,15 +8701,30 @@ mips_dwarf_register_span (rtx reg)
   rtx high, low;
   enum machine_mode mode;
 
-  /* By default, GCC maps increasing register numbers to increasing
-     memory locations, but paired FPRs are always little-endian,
-     regardless of the prevailing endianness.  */
+  /* TARGET_FLOATXX is implemented as 32-bit floating-point registers but
+     ensures that double precision registers are treated as if they were
+     64-bit physical registers.  The code will run correctly with 32-bit or
+     64-bit registers which means that dwarf information cannot be precisely
+     correct for all scenarios.  We choose to state that the 64-bit values
+     are stored in a single 64-bit 'piece'.  This slightly unusual
+     construct can then be interpreted as either a pair of registers if the
+     registers are 32-bit or a single 64-bit register depending on
+     hardware.  */
   mode = GET_MODE (reg);
   if (FP_REG_P (REGNO (reg))
-      && TARGET_BIG_ENDIAN
-      && MAX_FPRS_PER_FMT > 1
+      && TARGET_FLOATXX
       && GET_MODE_SIZE (mode) > UNITS_PER_FPREG)
     {
+      return gen_rtx_PARALLEL (VOIDmode, gen_rtvec (1, reg));
+    }
+  /* By default, GCC maps increasing register numbers to increasing
+     memory locations, but paired FPRs are always little-endian,
+     regardless of the prevailing endianness.  */
+  else if (FP_REG_P (REGNO (reg))
+	   && TARGET_BIG_ENDIAN
+	   && MAX_FPRS_PER_FMT > 1
+	   && GET_MODE_SIZE (mode) > UNITS_PER_FPREG)
+    {
       gcc_assert (GET_MODE_SIZE (mode) == UNITS_PER_HWFPVALUE);
       high = mips_subword (reg, true);
       low = mips_subword (reg, false);
@@ -8679,6 +8734,19 @@ mips_dwarf_register_span (rtx reg)
   return NULL_RTX;
 }
 
+/* Implement TARGET_DWARF_FRAME_REG_MODE.  */
+
+static enum machine_mode
+mips_dwarf_frame_reg_mode (int regno)
+{
+  enum machine_mode mode = default_dwarf_frame_reg_mode (regno);
+
+  if (FP_REG_P (regno) && mips_abi == ABI_32 && TARGET_FLOAT64)
+    mode = SImode;
+
+  return mode;
+}
+
 /* DSP ALU can bypass data with no delays for the following pairs. */
 enum insn_code dspalu_bypass_table[][2] =
 {
@@ -8958,6 +9026,31 @@ mips_file_start (void)
     fprintf (asm_out_file, "\t.nan\t%s\n",
 	     mips_nan == MIPS_IEEE_754_2008 ? "2008" : "legacy");
 
+#ifdef HAVE_AS_MODULE
+  /* Record the FP ABI.  See below for comments.  */
+  if (TARGET_NO_FLOAT)
+#ifdef HAVE_AS_GNU_ATTRIBUTE
+    fputs ("\t.gnu_attribute 4, 0\n", asm_out_file);
+#else
+    ;
+#endif
+  else if (!TARGET_HARD_FLOAT_ABI)
+    fputs ("\t.module\tsoftfloat\n", asm_out_file);
+  else if (!TARGET_DOUBLE_FLOAT)
+    fputs ("\t.module\tsinglefloat\n", asm_out_file);
+  else if (TARGET_FLOATXX)
+    fputs ("\t.module\tfp=xx\n", asm_out_file);
+  else if (TARGET_FLOAT64)
+    fputs ("\t.module\tfp=64\n", asm_out_file);
+  else
+    fputs ("\t.module\tfp=32\n", asm_out_file);
+
+  if (TARGET_ODD_SPREG)
+    fputs ("\t.module\toddspreg\n", asm_out_file);
+  else
+    fputs ("\t.module\tnooddspreg\n", asm_out_file);
+
+#else
 #ifdef HAVE_AS_GNU_ATTRIBUTE
   {
     int attr;
@@ -8971,9 +9064,19 @@ mips_file_start (void)
     /* Single-float code, -msingle-float.  */
     else if (!TARGET_DOUBLE_FLOAT)
       attr = 2;
-    /* 64-bit FP registers on a 32-bit target, -mips32r2 -mfp64.  */
-    else if (!TARGET_64BIT && TARGET_FLOAT64)
-      attr = 4;
+    /* 64-bit FP registers on a 32-bit target, -mips32r2 -mfp64.
+       Reserved attr=4.
+       This case used 12 callee save double precision registers
+       and is deprecated.  */
+    /* 64-bit or 32-bit FP registers on a 32-bit target, -mfpxx.  */
+    else if (TARGET_FLOATXX)
+      attr = 5;
+    /* 64-bit FP registers on a 32-bit target, -mfp64 -modd-spreg.  */
+    else if (mips_abi == ABI_32 && TARGET_FLOAT64 && TARGET_ODD_SPREG)
+      attr = 6;
+    /* 64-bit FP registers on a 32-bit target, -mfp64 -mno-odd-spreg.  */
+    else if (mips_abi == ABI_32 && TARGET_FLOAT64)
+      attr = 7;
     /* Regular FP code, FP regs same size as GP regs, -mdouble-float.  */
     else
       attr = 1;
@@ -8981,6 +9084,7 @@ mips_file_start (void)
     fprintf (asm_out_file, "\t.gnu_attribute 4, %d\n", attr);
   }
 #endif
+#endif
 
   /* If TARGET_ABICALLS, tell GAS to generate -KPIC code.  */
   if (TARGET_ABICALLS)
@@ -10469,7 +10573,9 @@ mips_for_each_saved_acc (HOST_WIDE_INT sp_offset, mips_save_restore_fn fn)
 static void
 mips_save_reg (rtx reg, rtx mem)
 {
-  if (GET_MODE (reg) == DFmode && !TARGET_FLOAT64)
+  if (GET_MODE (reg) == DFmode
+      && (!TARGET_FLOAT64
+	  || mips_abi == ABI_32))
     {
       rtx x1, x2;
 
@@ -10627,7 +10733,16 @@ mips_for_each_saved_gpr_and_fpr (HOST_WIDE_INT sp_offset,
        regno -= MAX_FPRS_PER_FMT)
     if (BITSET_P (cfun->machine->frame.fmask, regno - FP_REG_FIRST))
       {
-	mips_save_restore_reg (fpr_mode, regno, offset, fn);
+	if (!TARGET_FLOAT64 && TARGET_DOUBLE_FLOAT
+	    && (fixed_regs[regno] || fixed_regs[regno + 1]))
+	  {
+	    if (fixed_regs[regno])
+	      mips_save_restore_reg (SFmode, regno + 1, offset, fn);
+	    else
+	      mips_save_restore_reg (SFmode, regno, offset, fn);
+	  }
+	else
+	  mips_save_restore_reg (fpr_mode, regno, offset, fn);
 	offset -= GET_MODE_SIZE (fpr_mode);
       }
 }
@@ -11394,7 +11509,9 @@ mips_restore_reg (rtx reg, rtx mem)
      $7 instead and adjust the return insn appropriately.  */
   if (TARGET_MIPS16 && REGNO (reg) == RETURN_ADDR_REGNUM)
     reg = gen_rtx_REG (GET_MODE (reg), GP_REG_FIRST + 7);
-  else if (GET_MODE (reg) == DFmode && !TARGET_FLOAT64)
+  else if (GET_MODE (reg) == DFmode
+	   && (!TARGET_FLOAT64
+	       || mips_abi == ABI_32))
     {
       mips_add_cfa_restore (mips_subword (reg, true));
       mips_add_cfa_restore (mips_subword (reg, false));
@@ -11743,6 +11860,12 @@ mips_hard_regno_mode_ok_p (unsigned int regno, enum machine_mode mode)
       && (((regno - FP_REG_FIRST) % MAX_FPRS_PER_FMT) == 0
 	  || (MIN_FPRS_PER_FMT == 1 && size <= UNITS_PER_FPREG)))
     {
+      /* Deny use of odd-numbered registers for 32-bit data for
+	 the O32 FP64A ABI.  */
+      if (mips_abi == ABI_32 && TARGET_FLOAT64 && !TARGET_ODD_SPREG
+	  && size <= 4 && (regno & 1) != 0)
+	return false;
+
       /* Allow 64-bit vector modes for Loongson-2E/2F.  */
       if (TARGET_LOONGSON_VECTORS
 	  && (mode == V2SImode
@@ -12097,6 +12220,25 @@ mips_memory_move_cost (enum machine_mode mode, reg_class_t rclass, bool in)
 	  + memory_move_secondary_cost (mode, rclass, in));
 } 
 
+/* Implement SECONDARY_MEMORY_NEEDED.  */
+
+bool
+mips_secondary_memory_needed (enum reg_class class1, enum reg_class class2,
+			      enum machine_mode mode)
+{
+  /* Ignore spilled pseudos.  */
+  if (lra_in_progress && (class1 == NO_REGS || class2 == NO_REGS))
+    return false;
+
+  if (((class1 == FP_REGS) != (class2 == FP_REGS))
+      && ((TARGET_FLOATXX && !ISA_HAS_MXHC1)
+	  || (mips_abi == ABI_32 && TARGET_FLOAT64 && !TARGET_ODD_SPREG))
+      && GET_MODE_SIZE (mode) >= 8)
+    return true;
+
+  return false;
+}
+
 /* Return the register class required for a secondary register when
    copying between one of the registers in RCLASS and value X, which
    has mode MODE.  X is the source of the move if IN_P, otherwise it
@@ -17019,6 +17161,13 @@ mips_option_override (void)
 	target_flags &= ~MASK_FLOAT64;
     }
 
+  if (mips_abi != ABI_32 && TARGET_FLOATXX)
+    error ("%<-mfpxx%> can only be used with the o32 ABI");
+  else if (ISA_MIPS1 && !TARGET_FLOAT32)
+    error ("%<-march=%s%> requires %<-mfp32%>", mips_arch_info->name);
+  else if (TARGET_FLOATXX && !mips_lra_flag)
+    error ("%<-mfpxx%> requires %<-mlra%>");
+
   /* End of code shared with GAS.  */
 
   /* The R5900 FPU only supports single precision.  */
@@ -17106,6 +17255,26 @@ mips_option_override (void)
     warning (0, "the %qs architecture does not support madd or msub"
 	     " instructions", mips_arch_info->name);
 
+  /* If neither -modd-spreg nor -mno-odd-spreg was given on the command
+     line, set MASK_ODD_SPREG based on the ISA and ABI.  */
+  if ((target_flags_explicit & MASK_ODD_SPREG) == 0)
+    {
+      /* Disable TARGET_ODD_SPREG when using the O32 FPXX ABI.  */
+      if (!ISA_HAS_ODD_SPREG || TARGET_FLOATXX)
+	target_flags &= ~MASK_ODD_SPREG;
+      else
+	target_flags |= MASK_ODD_SPREG;
+    }
+  else if (TARGET_ODD_SPREG && !ISA_HAS_ODD_SPREG)
+    warning (0, "the %qs architecture does not support odd single-precision"
+	     " registers", mips_arch_info->name);
+
+  if (!TARGET_ODD_SPREG && TARGET_64BIT)
+    {
+      error ("unsupported combination: %s", "-mgp64 -mno-odd-spreg");
+      target_flags |= MASK_ODD_SPREG;
+    }
+
   /* The effect of -mabicalls isn't defined for the EABI.  */
   if (mips_abi == ABI_EABI && TARGET_ABICALLS)
     {
@@ -17468,8 +17637,10 @@ mips_conditional_register_usage (void)
 	call_really_used_regs[regno] = call_used_regs[regno] = 1;
     }
   /* Odd registers in the range $f21-$f31 (inclusive) are call-clobbered
-     for n32.  */
-  if (mips_abi == ABI_N32)
+     for n32 and o32 FP64.  */
+  if (mips_abi == ABI_N32
+      || (mips_abi == ABI_32
+          && TARGET_FLOAT64))
     {
       int regno;
       for (regno = FP_REG_FIRST + 21; regno <= FP_REG_FIRST + 31; regno+=2)
@@ -18807,6 +18978,21 @@ mips_expand_vec_minmax (rtx target, rtx op0, rtx op1,
   emit_insn (gen_rtx_SET (VOIDmode, target, x));
 }
 
+/* Implement HARD_REGNO_CALLER_SAVE_MODE.  */
+
+enum machine_mode
+mips_hard_regno_caller_save_mode (unsigned int regno,
+				  unsigned int nregs,
+				  enum machine_mode mode)
+{
+  /* For performance, to avoid saving/restoring upper parts of a register,
+     we return MODE as save mode when MODE is not VOIDmode.  */
+  if (mode == VOIDmode)
+    return choose_hard_reg_mode (regno, nregs, false);
+  else
+    return mode;
+}
+
 /* Implement TARGET_CASE_VALUES_THRESHOLD.  */
 
 unsigned int
@@ -19026,6 +19212,10 @@ mips_lra_p (void)
 #define TARGET_FUNCTION_ARG_ADVANCE mips_function_arg_advance
 #undef TARGET_FUNCTION_ARG_BOUNDARY
 #define TARGET_FUNCTION_ARG_BOUNDARY mips_function_arg_boundary
+#undef TARGET_GET_RAW_RESULT_MODE
+#define TARGET_GET_RAW_RESULT_MODE mips_get_reg_raw_mode
+#undef TARGET_GET_RAW_ARG_MODE
+#define TARGET_GET_RAW_ARG_MODE mips_get_reg_raw_mode
 
 #undef TARGET_MODE_REP_EXTENDED
 #define TARGET_MODE_REP_EXTENDED mips_mode_rep_extended
@@ -19082,6 +19272,8 @@ mips_lra_p (void)
 #endif
 #undef TARGET_DWARF_REGISTER_SPAN
 #define TARGET_DWARF_REGISTER_SPAN mips_dwarf_register_span
+#undef TARGET_DWARF_FRAME_REG_MODE
+#define TARGET_DWARF_FRAME_REG_MODE mips_dwarf_frame_reg_mode
 
 #undef TARGET_ASM_FINAL_POSTSCAN_INSN
 #define TARGET_ASM_FINAL_POSTSCAN_INSN mips_final_postscan_insn
diff --git a/gcc/config/mips/mips.h b/gcc/config/mips/mips.h
index 9a15287..3092f86 100644
--- a/gcc/config/mips/mips.h
+++ b/gcc/config/mips/mips.h
@@ -320,6 +320,10 @@ struct mips_cpu_info {
 #define TARGET_HARD_FLOAT (TARGET_HARD_FLOAT_ABI && !TARGET_MIPS16)
 #define TARGET_SOFT_FLOAT (TARGET_SOFT_FLOAT_ABI || TARGET_MIPS16)
 
+/* TARGET_FLOAT64 represents -mfp64 and TARGET_FLOATXX represents
+   -mfpxx, derive TARGET_FLOAT32 to represent -mfp32.  */
+#define TARGET_FLOAT32 (!TARGET_FLOAT64 && !TARGET_FLOATXX)
+
 /* False if SC acts as a memory barrier with respect to itself,
    otherwise a SYNC will be emitted after SC for atomic operations
    that require ordering between the SC and following loads and
@@ -388,6 +392,8 @@ struct mips_cpu_info {
 									\
       if (TARGET_FLOAT64)						\
 	builtin_define ("__mips_fpr=64");				\
+      else if (TARGET_FLOATXX)						\
+	builtin_define ("__mips_fpr=0");				\
       else								\
 	builtin_define ("__mips_fpr=32");				\
 									\
@@ -516,6 +522,8 @@ struct mips_cpu_info {
       builtin_define_with_int_value ("_MIPS_SZPTR", POINTER_SIZE);	\
       builtin_define_with_int_value ("_MIPS_FPSET",			\
 				     32 / MAX_FPRS_PER_FMT);		\
+      builtin_define_with_int_value ("_MIPS_SPFPSET",			\
+				     TARGET_ODD_SPREG ? 32 : 16);	\
 									\
       /* These defines reflect the ABI in use, not whether the  	\
 	 FPU is directly accessible.  */				\
@@ -751,6 +759,12 @@ struct mips_cpu_info {
 #define MIPS_32BIT_OPTION_SPEC \
   "mips1|mips2|mips32*|mgp32"
 
+/* A spec condition that matches architectures should be targetted with
+   O32 FPXX for compatibility reasons.  */
+#define MIPS_FPXX_OPTION_SPEC \
+  "mips2|mips3|mips4|mips5|mips32|mips32r2|mips32r3|mips32r5| \
+   mips64|mips64r2|mips64r3|mips64r5"
+
 /* Infer a -msynci setting from a -mips argument, on the assumption that
    -msynci is desired where possible.  */
 #define MIPS_ISA_SYNCI_SPEC \
@@ -776,6 +790,8 @@ struct mips_cpu_info {
    --with-float is ignored if -mhard-float or -msoft-float are
      specified.
    --with-nan is ignored if -mnan is specified.
+   --with-fp-32 is ignored if -mfp is specified.
+   --with-odd-spreg-32 is ignored if -modd-spreg or -mno-odd-spreg are specified.
    --with-divide is ignored if -mdivide-traps or -mdivide-breaks are
      specified. */
 #define OPTION_DEFAULT_SPECS \
@@ -789,6 +805,8 @@ struct mips_cpu_info {
   {"float", "%{!msoft-float:%{!mhard-float:-m%(VALUE)-float}}" }, \
   {"fpu", "%{!msingle-float:%{!mdouble-float:-m%(VALUE)-float}}" }, \
   {"nan", "%{!mnan=*:-mnan=%(VALUE)}" }, \
+  {"fp_32", "%{" OPT_ARCH32 ":%{!mfp*:-mfp%(VALUE)}}" }, \
+  {"odd_spreg_32", "%{" OPT_ARCH32 ":%{!modd-spreg:%{!mno-odd-spreg:-m%(VALUE)}}}" }, \
   {"divide", "%{!mdivide-traps:%{!mdivide-breaks:-mdivide-%(VALUE)}}" }, \
   {"llsc", "%{!mllsc:%{!mno-llsc:-m%(VALUE)}}" }, \
   {"mips-plt", "%{!mplt:%{!mno-plt:-m%(VALUE)}}" }, \
@@ -840,6 +858,12 @@ struct mips_cpu_info {
    been generated up to this point.  */
 #define ISA_HAS_BRANCHLIKELY	(!ISA_MIPS1)
 
+/* ISA has 32 single-precision registers.  */
+#define ISA_HAS_ODD_SPREG	((mips_isa_rev >= 1			\
+				  && !TARGET_LOONGSON_3A)		\
+				 || TARGET_FLOAT64			\
+				 || TARGET_MIPS5900)
+
 /* ISA has a three-operand multiplication instruction (usually spelt "mul").  */
 #define ISA_HAS_MUL3		((TARGET_MIPS3900                       \
 				  || TARGET_MIPS5400			\
@@ -1027,7 +1051,8 @@ struct mips_cpu_info {
 #define ISA_HAS_EXT_INS		(mips_isa_rev >= 2 && !TARGET_MIPS16)
 
 /* ISA has instructions for accessing top part of 64-bit fp regs.  */
-#define ISA_HAS_MXHC1		(TARGET_FLOAT64 && mips_isa_rev >= 2)
+#define ISA_HAS_MXHC1		(!TARGET_FLOAT32	\
+				 && mips_isa_rev >= 2)
 
 /* ISA has lwxs instruction (load w/scaled index address.  */
 #define ISA_HAS_LWXS		((TARGET_SMARTMIPS || TARGET_MICROMIPS) \
@@ -1183,7 +1208,8 @@ struct mips_cpu_info {
 %(subtarget_asm_debugging_spec) \
 %{mabi=*} %{!mabi=*: %(asm_abi_default_spec)} \
 %{mgp32} %{mgp64} %{march=*} %{mxgot:-xgot} \
-%{mfp32} %{mfp64} %{mnan=*} \
+%{mfp32} %{mfpxx} %{mfp64} %{mnan=*} \
+%{modd-spreg} %{mno-odd-spreg} \
 %{mshared} %{mno-shared} \
 %{msym32} %{mno-sym32} \
 %{mtune=*} \
@@ -1355,7 +1381,7 @@ struct mips_cpu_info {
 /* The number of consecutive floating-point registers needed to store the
    smallest format supported by the FPU.  */
 #define MIN_FPRS_PER_FMT \
-  (mips_isa_rev >= 1 ? 1 : MAX_FPRS_PER_FMT)
+  (TARGET_ODD_SPREG ? 1 : MAX_FPRS_PER_FMT)
 
 /* The largest size of value that can be held in floating-point
    registers and moved with a single instruction.  */
@@ -1770,6 +1796,16 @@ struct mips_cpu_info {
 #define HARD_REGNO_MODE_OK(REGNO, MODE)					\
   mips_hard_regno_mode_ok[ (int)(MODE) ][ (REGNO) ]
 
+/* Select a register mode required for caller save of hard regno REGNO.  */
+#define HARD_REGNO_CALLER_SAVE_MODE(REGNO, NREGS, MODE) \
+  mips_hard_regno_caller_save_mode (REGNO, NREGS, MODE)
+
+/* Odd-numbered single-precision registers are not considered call saved
+   for O32 FPXX as they will be clobbered when run on an FR=1 FPU.  */
+#define HARD_REGNO_CALL_PART_CLOBBERED(REGNO, MODE)			\
+  (TARGET_FLOATXX && hard_regno_nregs[REGNO][MODE] == 1			\
+   && FP_REG_P (REGNO) && (REGNO & 1))
+
 #define MODES_TIEABLE_P mips_modes_tieable_p
 
 /* Register to use for pushing function arguments.  */
@@ -2103,6 +2139,22 @@ enum reg_class
 #define SECONDARY_OUTPUT_RELOAD_CLASS(CLASS, MODE, X)			\
   mips_secondary_reload_class (CLASS, MODE, X, false)
 
+/* When targetting the O32 FPXX ABI then all doubleword or greater moves
+   to/from FP registers must be performed by FR-mode-aware instructions.
+   This can be achieved using mfhc1/mthc1 when these instructions are
+   available but otherwise moves must go via memory.
+   For the O32 FP64A ABI then all odd-numbered doubleword or greater
+   moves to/from FP registers must move via memory as it is not permitted
+   to access the lower-half of these registers with mtc1/mfc1 since that
+   constitutes a single-precision access (which is forbidden).  This is
+   implemented by requiring all double-word moves to move via memory
+   as this check is register class based and not register based.
+   Splitting the FP_REGS into even and odd classes would allow the
+   precise restriction to be represented but this would have a
+   significant effect on other areas of the backend.  */
+#define SECONDARY_MEMORY_NEEDED(CLASS1, CLASS2, MODE)			\
+  mips_secondary_memory_needed ((CLASS1), (CLASS2), (MODE))
+
 /* Return the maximum number of consecutive registers
    needed to represent mode MODE in a register of class CLASS.  */
 
@@ -2224,12 +2276,16 @@ enum reg_class
   (TARGET_MIPS16 ? GP_ARG_FIRST + 2 : PIC_OFFSET_TABLE_REGNUM)
 
 /* 1 if N is a possible register number for function argument passing.
-   We have no FP argument registers when soft-float.  When FP registers
-   are 32 bits, we can't directly reference the odd numbered ones.  */
+   We have no FP argument registers when soft-float.  Special handling
+   is required for O32 where only even numbered registers are used for
+   O32-FPXX and O32-FP64.  */
 
 #define FUNCTION_ARG_REGNO_P(N)					\
   ((IN_RANGE((N), GP_ARG_FIRST, GP_ARG_LAST)			\
-    || (IN_RANGE((N), FP_ARG_FIRST, FP_ARG_LAST)))		\
+    || (IN_RANGE((N), FP_ARG_FIRST, FP_ARG_LAST) 		\
+        && (mips_abi != ABI_32 					\
+            || TARGET_FLOAT32 					\
+            || ((N) % 2 == 0))))				\
    && !fixed_regs[N])
 
 /* This structure has to cope with two different argument allocation
diff --git a/gcc/config/mips/mips.md b/gcc/config/mips/mips.md
index 7e23bd2..918b051 100644
--- a/gcc/config/mips/mips.md
+++ b/gcc/config/mips/mips.md
@@ -432,11 +432,21 @@ (define_attr "compression" "none,all,micromips"
   (const_string "none"))
 
 (define_attr "enabled" "no,yes"
-  (if_then_else (ior (eq_attr "compression" "all,none")
-		     (and (eq_attr "compression" "micromips")
-	                  (match_test "TARGET_MICROMIPS")))
-	        (const_string "yes")
-	        (const_string "no")))
+  (cond [;; The O32 FPXX and FP64A ABI extensions prohibit direct moves between
+	 ;; GR_REG and FR_REG for 64-bit values.
+	 (and (eq_attr "move_type" "mtc,mfc")
+	      (match_test "(TARGET_FLOATXX && !ISA_HAS_MXHC1)
+			   || (mips_abi == ABI_32
+			       && TARGET_FLOAT64 && !TARGET_ODD_SPREG)")
+	      (eq_attr "dword_mode" "yes"))
+	 (const_string "no")
+
+	 ;; The micromips compressed instruction alternatives should only be
+	 ;; considered when targetting micromips.
+	 (and (eq_attr "compression" "micromips")
+	      (match_test "!TARGET_MICROMIPS"))
+	 (const_string "no")]
+	(const_string "yes")))
 
 ;; The number of individual instructions that a non-branch pattern generates,
 ;; using units of BASE_INSN_LENGTH.
@@ -4996,7 +5006,7 @@ (define_expand "move_doubleword_fpr<mode>"
       rtx low = mips_subword (operands[1], 0);
       rtx high = mips_subword (operands[1], 1);
       emit_insn (gen_load_low<mode> (operands[0], low));
-      if (TARGET_FLOAT64 && !TARGET_64BIT)
+      if (ISA_HAS_MXHC1 && !TARGET_64BIT)
       	emit_insn (gen_mthc1<mode> (operands[0], high, operands[0]));
       else
 	emit_insn (gen_load_high<mode> (operands[0], high, operands[0]));
@@ -5006,7 +5016,7 @@ (define_expand "move_doubleword_fpr<mode>"
       rtx low = mips_subword (operands[0], 0);
       rtx high = mips_subword (operands[0], 1);
       emit_insn (gen_store_word<mode> (low, operands[1], const0_rtx));
-      if (TARGET_FLOAT64 && !TARGET_64BIT)
+      if (ISA_HAS_MXHC1 && !TARGET_64BIT)
 	emit_insn (gen_mfhc1<mode> (high, operands[1]));
       else
 	emit_insn (gen_store_word<mode> (high, operands[1], const1_rtx));
diff --git a/gcc/config/mips/mips.opt b/gcc/config/mips/mips.opt
index dca4f80..3558171 100644
--- a/gcc/config/mips/mips.opt
+++ b/gcc/config/mips/mips.opt
@@ -197,6 +197,10 @@ mfp32
 Target Report RejectNegative InverseMask(FLOAT64)
 Use 32-bit floating-point registers
 
+mfpxx
+Target Report RejectNegative Mask(FLOATXX)
+Follow the O32 FPXX ABI
+
 mfp64
 Target Report RejectNegative Mask(FLOAT64)
 Use 64-bit floating-point registers
@@ -408,5 +412,9 @@ mxgot
 Target Report Var(TARGET_XGOT)
 Lift restrictions on GOT size
 
+modd-spreg
+Target Report Mask(ODD_SPREG)
+Enable use of odd-numbered single-precision registers
+
 noasmopt
 Driver
diff --git a/gcc/config/mips/mti-elf.h b/gcc/config/mips/mti-elf.h
index 76d289e..9b8076c 100644
--- a/gcc/config/mips/mti-elf.h
+++ b/gcc/config/mips/mti-elf.h
@@ -34,6 +34,9 @@ along with GCC; see the file COPYING3.  If not see
      or -mgp setting.  */						\
   "%{!mabi=*: %{" MIPS_32BIT_OPTION_SPEC ": -mabi=32;: -mabi=n32}}",	\
 									\
+  /* If no FP option is specified, infer one from the ABI/ISA level.  */\
+  "%{!mfp*: %{mabi=32: %{" MIPS_FPXX_OPTION_SPEC ": -mfpxx}}}",		\
+									\
   /* Make sure that an endian option is always present.  This makes	\
      things like LINK_SPEC easier to write.  */				\
   "%{!EB:%{!EL:%(endian_spec)}}",					\
diff --git a/gcc/config/mips/mti-linux.h b/gcc/config/mips/mti-linux.h
index 318e981..e8ff184 100644
--- a/gcc/config/mips/mti-linux.h
+++ b/gcc/config/mips/mti-linux.h
@@ -39,6 +39,9 @@ along with GCC; see the file COPYING3.  If not see
      or -mgp setting.  */						\
   "%{!mabi=*: %{" MIPS_32BIT_OPTION_SPEC ": -mabi=32;: -mabi=n32}}",	\
 									\
+  /* If no FP option is specified, infer one from the ABI/ISA level.  */\
+  "%{!mfp*: %{mabi=32: %{" MIPS_FPXX_OPTION_SPEC ": -mfpxx}}}",		\
+									\
   /* Base SPECs.  */							\
   BASE_DRIVER_SELF_SPECS						\
 									\
diff --git a/gcc/configure b/gcc/configure
index fe2e647..c8ed7e7 100755
--- a/gcc/configure
+++ b/gcc/configure
@@ -26072,6 +26072,41 @@ $as_echo "#define HAVE_AS_GNU_ATTRIBUTE 1" >>confdefs.h
 
 fi
 
+    { $as_echo "$as_me:${as_lineno-$LINENO}: checking assembler for .module support" >&5
+$as_echo_n "checking assembler for .module support... " >&6; }
+if test "${gcc_cv_as_mips_module+set}" = set; then :
+  $as_echo_n "(cached) " >&6
+else
+  gcc_cv_as_mips_module=no
+  if test x$gcc_cv_as != x; then
+    $as_echo '.module fp=32' > conftest.s
+    if { ac_try='$gcc_cv_as $gcc_cv_as_flags  -o conftest.o conftest.s >&5'
+  { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_try\""; } >&5
+  (eval $ac_try) 2>&5
+  ac_status=$?
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; }; }
+    then
+	gcc_cv_as_mips_module=yes
+    else
+      echo "configure: failed program was" >&5
+      cat conftest.s >&5
+    fi
+    rm -f conftest.o conftest.s
+  fi
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $gcc_cv_as_mips_module" >&5
+$as_echo "$gcc_cv_as_mips_module" >&6; }
+if test $gcc_cv_as_mips_module = yes; then
+
+$as_echo "#define HAVE_AS_MODULE 1" >>confdefs.h
+
+fi
+    if test x$gcc_cv_as_mips_module = xno \
+       && test x$with_fp != x; then
+      as_fn_error "Requesting --with-fp= requires assembler support for .module." "$LINENO" 5
+    fi
+
     { $as_echo "$as_me:${as_lineno-$LINENO}: checking assembler for .micromips support" >&5
 $as_echo_n "checking assembler for .micromips support... " >&6; }
 if test "${gcc_cv_as_micromips_support+set}" = set; then :
diff --git a/gcc/configure.ac b/gcc/configure.ac
index ea8eca4..dac1d09 100644
--- a/gcc/configure.ac
+++ b/gcc/configure.ac
@@ -4197,6 +4197,17 @@ LCF0:
       [AC_DEFINE(HAVE_AS_GNU_ATTRIBUTE, 1,
 	  [Define if your assembler supports .gnu_attribute.])])
 
+    gcc_GAS_CHECK_FEATURE([.module support],
+      gcc_cv_as_mips_module,,,
+      [.module fp=32],,
+      [AC_DEFINE(HAVE_AS_MODULE, 1,
+	  [Define if yout assembler supports .module.])])
+    if test x$gcc_cv_as_mips_module = xno \
+       && test x$with_fp != x; then
+      AC_MSG_ERROR(
+	[Requesting --with-fp= requires assembler support for .module.])
+    fi
+
     gcc_GAS_CHECK_FEATURE([.micromips support],
       gcc_cv_as_micromips_support,,[--fatal-warnings],
       [.set micromips],,
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index 6374261..64ee23e 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -782,8 +782,9 @@ Objective-C and Objective-C++ Dialects}.
 -minterlink-mips16  -mno-interlink-mips16 @gol
 -mabi=@var{abi}  -mabicalls  -mno-abicalls @gol
 -mshared  -mno-shared  -mplt  -mno-plt  -mxgot  -mno-xgot @gol
--mgp32  -mgp64  -mfp32  -mfp64  -mhard-float  -msoft-float @gol
+-mgp32  -mgp64  -mfp32  mfpxx -mfp64  -mhard-float  -msoft-float @gol
 -mno-float  -msingle-float  -mdouble-float @gol
+-modd-spreg -mno-odd-spreg @gol
 -mabs=@var{mode}  -mnan=@var{encoding} @gol
 -mdsp  -mno-dsp  -mdspr2  -mno-dspr2 @gol
 -mmcu -mmno-mcu @gol
@@ -17513,7 +17514,20 @@ same, but each scalar value is passed in a single 64-bit register
 rather than a pair of 32-bit registers.  For example, scalar
 floating-point values are returned in @samp{$f0} only, not a
 @samp{$f0}/@samp{$f1} pair.  The set of call-saved registers also
-remains the same, but all 64 bits are saved.
+remains the same in that the even-numbered double-precision registers
+are saved.
+
+A further two variants of the o32 ABI are also supported to enable
+a transition from 32-bit to 64-bit registers.  These are FPXX
+(@option{-mfpxx}) and FP64A (@option{-mfp64} @option{-mno-odd-spreg}).
+The FPXX extension mandates that all code must execute correctly
+whether run using 32-bit or 64-bit registers.  The code can also
+interlink with either FP32 or FP64, but not both simulataneously.
+The FP64A extension is similar to the FP64 extension but forbids the
+use of odd-numbered single-precision registers.  This can be used
+in conjunction with the @code{FRE} mode of FPUs in MIPS32R5
+processors and allows both FP32 and FP64A code to interlink and
+run in the same process without changing FPU modes.
 
 @item -mabicalls
 @itemx -mno-abicalls
@@ -17602,6 +17616,11 @@ Assume that floating-point registers are 32 bits wide.
 @opindex mfp64
 Assume that floating-point registers are 64 bits wide.
 
+@item -mfpxx
+@opindex mfpxx
+Do not assume that floating-point registers are specifically 32 bits or
+64 bits wide.
+
 @item -mhard-float
 @opindex mhard-float
 Use floating-point coprocessor instructions.
@@ -17633,6 +17652,15 @@ operations.
 Assume that the floating-point coprocessor supports double-precision
 operations.  This is the default.
 
+@item -modd-spreg
+@itemx -mno-odd-spreg
+@opindex modd-spreg
+@opindex mno-odd-spreg
+Enable the use of odd-numbered single-precision floating-point registers
+for the o32 ABI.  This is the default for specific processors that are
+known to support these registers, however the o32 FPXX extension sets
+@code{-mno-odd-spreg} by default.
+
 @item -mabs=2008
 @itemx -mabs=legacy
 @opindex mabs=2008
diff --git a/gcc/testsuite/gcc.target/mips/args-1.c b/gcc/testsuite/gcc.target/mips/args-1.c
index 3a132de..643df24 100644
--- a/gcc/testsuite/gcc.target/mips/args-1.c
+++ b/gcc/testsuite/gcc.target/mips/args-1.c
@@ -5,7 +5,7 @@
 const char *compiled_for = _MIPS_ARCH;
 const char *optimized_for = _MIPS_TUNE;
 
-#if __mips_fpr != 32 && __mips_fpr != 64
+#if __mips_fpr != 32 && __mips_fpr != 64 && __mips_fpr != 0
 #error Bad __mips_fpr
 #endif
 
diff --git a/gcc/testsuite/gcc.target/mips/call-clobbered-1.c b/gcc/testsuite/gcc.target/mips/call-clobbered-1.c
new file mode 100644
index 0000000..ecb994f
--- /dev/null
+++ b/gcc/testsuite/gcc.target/mips/call-clobbered-1.c
@@ -0,0 +1,21 @@
+/* Check that we handle call-clobbered FPRs correctly.  */
+/* { dg-skip-if "code quality test" { *-*-* } { "-O0" } { "" } } */
+/* { dg-options "isa>=2 -mabi=32 -ffixed-f0 -ffixed-f1 -ffixed-f2 -ffixed-f3 -ffixed-f4 -ffixed-f5 -ffixed-f6 -ffixed-f7 -ffixed-f8 -ffixed-f9 -ffixed-f10 -ffixed-f11 -ffixed-f12 -ffixed-f13 -ffixed-f14 -ffixed-f15 -ffixed-f16 -ffixed-f17 -ffixed-f18 -ffixed-f19" } */
+
+void bar (void);
+double a;
+double
+foo ()
+{
+  double b = a + 1.0;
+  bar();
+  return b;
+}
+/* { dg-final { scan-assembler-not "lwc1" } } */
+/* { dg-final { scan-assembler-not "swc1" } } */
+/* { dg-final { scan-assembler-times "sdc1" 2 } } */
+/* { dg-final { scan-assembler-times "ldc1" 4 } } */
+/* { dg-final { scan-assembler-not "mtc" } } */
+/* { dg-final { scan-assembler-not "mfc" } } */
+/* { dg-final { scan-assembler-not "mthc" } } */
+/* { dg-final { scan-assembler-not "mfhc" } } */
diff --git a/gcc/testsuite/gcc.target/mips/call-clobbered-2.c b/gcc/testsuite/gcc.target/mips/call-clobbered-2.c
new file mode 100644
index 0000000..5f9a472
--- /dev/null
+++ b/gcc/testsuite/gcc.target/mips/call-clobbered-2.c
@@ -0,0 +1,21 @@
+/* Check that we handle call-clobbered FPRs correctly.  */
+/* { dg-skip-if "code quality test" { *-*-* } { "-O0" } { "" } } */
+/* { dg-options "-mabi=32 -modd-spreg -mfp32 -ffixed-f0 -ffixed-f1 -ffixed-f2 -ffixed-f3 -ffixed-f4 -ffixed-f5 -ffixed-f6 -ffixed-f7 -ffixed-f8 -ffixed-f9 -ffixed-f10 -ffixed-f11 -ffixed-f12 -ffixed-f13 -ffixed-f14 -ffixed-f15 -ffixed-f16 -ffixed-f17 -ffixed-f18 -ffixed-f19 -ffixed-f20 -ffixed-f22 -ffixed-f24 -ffixed-f26 -ffixed-f28 -ffixed-f30" } */
+
+void bar (void);
+float a;
+float
+foo ()
+{
+  float b = a + 1.0f;
+  bar();
+  return b;
+}
+/* { dg-final { scan-assembler-times "lwc1" 4 } } */
+/* { dg-final { scan-assembler-times "swc1" 2 } } */
+/* { dg-final { scan-assembler-not "mtc" } } */
+/* { dg-final { scan-assembler-not "mfc" } } */
+/* { dg-final { scan-assembler-not "mthc" } } */
+/* { dg-final { scan-assembler-not "mfhc" } } */
+/* { dg-final { scan-assembler-not "sdc1" } } */
+/* { dg-final { scan-assembler-not "ldc1" } } */
diff --git a/gcc/testsuite/gcc.target/mips/call-clobbered-3.c b/gcc/testsuite/gcc.target/mips/call-clobbered-3.c
new file mode 100644
index 0000000..fce4d99
--- /dev/null
+++ b/gcc/testsuite/gcc.target/mips/call-clobbered-3.c
@@ -0,0 +1,23 @@
+/* Check that we handle call-clobbered FPRs correctly.  */
+/* { dg-skip-if "code quality test" { *-*-* } { "-O0" } { "" } } */
+/* Refer to call-clobbered-4.c to see the expected output from -Os builds.  */
+/* { dg-skip-if "uses callee-saved GPR" { *-*-* } { "-Os" } { "" } } */
+/* { dg-options "-mabi=32 -modd-spreg -mfpxx -ffixed-f0 -ffixed-f1 -ffixed-f2 -ffixed-f3 -ffixed-f4 -ffixed-f5 -ffixed-f6 -ffixed-f7 -ffixed-f8 -ffixed-f9 -ffixed-f10 -ffixed-f11 -ffixed-f12 -ffixed-f13 -ffixed-f14 -ffixed-f15 -ffixed-f16 -ffixed-f17 -ffixed-f18 -ffixed-f19 -ffixed-f20 -ffixed-f22 -ffixed-f24 -ffixed-f26 -ffixed-f28 -ffixed-f30" } */
+
+void bar (void);
+float a;
+float
+foo ()
+{
+  float b = a + 1.0f;
+  bar();
+  return b;
+}
+/* { dg-final { scan-assembler-times "lwc1" 5 } } */
+/* { dg-final { scan-assembler-times "swc1" 3 } } */
+/* { dg-final { scan-assembler-not "mtc" } } */
+/* { dg-final { scan-assembler-not "mfc" } } */
+/* { dg-final { scan-assembler-not "mthc" } } */
+/* { dg-final { scan-assembler-not "mfhc" } } */
+/* { dg-final { scan-assembler-not "ldc1" } } */
+/* { dg-final { scan-assembler-not "sdc1" } } */
diff --git a/gcc/testsuite/gcc.target/mips/call-clobbered-4.c b/gcc/testsuite/gcc.target/mips/call-clobbered-4.c
new file mode 100644
index 0000000..51498b8
--- /dev/null
+++ b/gcc/testsuite/gcc.target/mips/call-clobbered-4.c
@@ -0,0 +1,23 @@
+/* Check that we handle call-clobbered FPRs correctly.
+   This test differs from call-clobbered-3.c because when optimising for size
+   a callee-saved GPR is used for 'b' to cross the call.  */
+/* { dg-skip-if "code quality test" { *-*-* } { "*" } { "-Os" } } */
+/* { dg-options "-mabi=32 -modd-spreg -mfpxx -ffixed-f0 -ffixed-f1 -ffixed-f2 -ffixed-f3 -ffixed-f4 -ffixed-f5 -ffixed-f6 -ffixed-f7 -ffixed-f8 -ffixed-f9 -ffixed-f10 -ffixed-f11 -ffixed-f12 -ffixed-f13 -ffixed-f14 -ffixed-f15 -ffixed-f16 -ffixed-f17 -ffixed-f18 -ffixed-f19 -ffixed-f20 -ffixed-f22 -ffixed-f24 -ffixed-f26 -ffixed-f28 -ffixed-f30" } */
+
+void bar (void);
+float a;
+float
+foo ()
+{
+  float b = a + 1.0f;
+  bar();
+  return b;
+}
+/* { dg-final { scan-assembler-times "lwc1" 4 } } */
+/* { dg-final { scan-assembler-times "swc1" 2 } } */
+/* { dg-final { scan-assembler-times "mtc" 1 } } */
+/* { dg-final { scan-assembler-times "mfc" 1 } } */
+/* { dg-final { scan-assembler-not "mthc" } } */
+/* { dg-final { scan-assembler-not "mfhc" } } */
+/* { dg-final { scan-assembler-not "ldc1" } } */
+/* { dg-final { scan-assembler-not "sdc1" } } */
diff --git a/gcc/testsuite/gcc.target/mips/call-clobbered-5.c b/gcc/testsuite/gcc.target/mips/call-clobbered-5.c
new file mode 100644
index 0000000..c7cd7ca
--- /dev/null
+++ b/gcc/testsuite/gcc.target/mips/call-clobbered-5.c
@@ -0,0 +1,21 @@
+/* Check that we handle call-clobbered FPRs correctly.  */
+/* { dg-skip-if "code quality test" { *-*-* } { "-O0" } { "" } } */
+/* { dg-options "-mabi=32 -mfp64 -ffixed-f0 -ffixed-f1 -ffixed-f2 -ffixed-f3 -ffixed-f4 -ffixed-f5 -ffixed-f6 -ffixed-f7 -ffixed-f8 -ffixed-f9 -ffixed-f10 -ffixed-f11 -ffixed-f12 -ffixed-f13 -ffixed-f14 -ffixed-f15 -ffixed-f16 -ffixed-f17 -ffixed-f18 -ffixed-f19 -ffixed-f20 -ffixed-f22 -ffixed-f24 -ffixed-f26 -ffixed-f28 -ffixed-f30" } */
+
+void bar (void);
+float a;
+float
+foo ()
+{
+  float b = a + 1.0f;
+  bar();
+  return b;
+}
+/* { dg-final { scan-assembler-times "lwc1" 3 } } */
+/* { dg-final { scan-assembler-times "swc1" 1 } } */
+/* { dg-final { scan-assembler-not "sdc1" } } */
+/* { dg-final { scan-assembler-not "ldc1" } } */
+/* { dg-final { scan-assembler-not "mtc" } } */
+/* { dg-final { scan-assembler-not "mfc" } } */
+/* { dg-final { scan-assembler-not "mthc" } } */
+/* { dg-final { scan-assembler-not "mfhc" } } */
diff --git a/gcc/testsuite/gcc.target/mips/call-saved-4.c b/gcc/testsuite/gcc.target/mips/call-saved-4.c
new file mode 100644
index 0000000..e126175
--- /dev/null
+++ b/gcc/testsuite/gcc.target/mips/call-saved-4.c
@@ -0,0 +1,32 @@
+/* Check that we save the correct call-saved GPRs and FPRs.  */
+/* { dg-options "isa>=2 -mabi=32 -mfp32" } */
+
+void bar (void);
+
+void
+foo (int x)
+{
+  __builtin_unwind_init ();
+  __builtin_eh_return (x, bar);
+}
+/* { dg-final { scan-assembler "\\\$16" } } */
+/* { dg-final { scan-assembler "\\\$17" } } */
+/* { dg-final { scan-assembler "\\\$18" } } */
+/* { dg-final { scan-assembler "\\\$19" } } */
+/* { dg-final { scan-assembler "\\\$20" } } */
+/* { dg-final { scan-assembler "\\\$21" } } */
+/* { dg-final { scan-assembler "\\\$22" } } */
+/* { dg-final { scan-assembler "\\\$23" } } */
+/* { dg-final { scan-assembler "\\\$(30|fp)" } } */
+/* { dg-final { scan-assembler "\\\$f20" } } */
+/* { dg-final { scan-assembler "\\\$f22" } } */
+/* { dg-final { scan-assembler "\\\$f24" } } */
+/* { dg-final { scan-assembler "\\\$f26" } } */
+/* { dg-final { scan-assembler "\\\$f28" } } */
+/* { dg-final { scan-assembler "\\\$f30" } } */
+/* { dg-final { scan-assembler-not "\\\$f21" } } */
+/* { dg-final { scan-assembler-not "\\\$f23" } } */
+/* { dg-final { scan-assembler-not "\\\$f25" } } */
+/* { dg-final { scan-assembler-not "\\\$f27" } } */
+/* { dg-final { scan-assembler-not "\\\$f29" } } */
+/* { dg-final { scan-assembler-not "\\\$f31" } } */
diff --git a/gcc/testsuite/gcc.target/mips/call-saved-5.c b/gcc/testsuite/gcc.target/mips/call-saved-5.c
new file mode 100644
index 0000000..2937b31
--- /dev/null
+++ b/gcc/testsuite/gcc.target/mips/call-saved-5.c
@@ -0,0 +1,32 @@
+/* Check that we save the correct call-saved GPRs and FPRs.  */
+/* { dg-options "-mabi=32 -mfpxx" } */
+
+void bar (void);
+
+void
+foo (int x)
+{
+  __builtin_unwind_init ();
+  __builtin_eh_return (x, bar);
+}
+/* { dg-final { scan-assembler "\\\$16" } } */
+/* { dg-final { scan-assembler "\\\$17" } } */
+/* { dg-final { scan-assembler "\\\$18" } } */
+/* { dg-final { scan-assembler "\\\$19" } } */
+/* { dg-final { scan-assembler "\\\$20" } } */
+/* { dg-final { scan-assembler "\\\$21" } } */
+/* { dg-final { scan-assembler "\\\$22" } } */
+/* { dg-final { scan-assembler "\\\$23" } } */
+/* { dg-final { scan-assembler "\\\$(30|fp)" } } */
+/* { dg-final { scan-assembler "\\\$f20" } } */
+/* { dg-final { scan-assembler "\\\$f22" } } */
+/* { dg-final { scan-assembler "\\\$f24" } } */
+/* { dg-final { scan-assembler "\\\$f26" } } */
+/* { dg-final { scan-assembler "\\\$f28" } } */
+/* { dg-final { scan-assembler "\\\$f30" } } */
+/* { dg-final { scan-assembler-not "\\\$f21" } } */
+/* { dg-final { scan-assembler-not "\\\$f23" } } */
+/* { dg-final { scan-assembler-not "\\\$f25" } } */
+/* { dg-final { scan-assembler-not "\\\$f27" } } */
+/* { dg-final { scan-assembler-not "\\\$f29" } } */
+/* { dg-final { scan-assembler-not "\\\$f31" } } */
diff --git a/gcc/testsuite/gcc.target/mips/call-saved-6.c b/gcc/testsuite/gcc.target/mips/call-saved-6.c
new file mode 100644
index 0000000..0d1a4c8
--- /dev/null
+++ b/gcc/testsuite/gcc.target/mips/call-saved-6.c
@@ -0,0 +1,32 @@
+/* Check that we save the correct call-saved GPRs and FPRs.  */
+/* { dg-options "-mabi=32 -mfp64" } */
+
+void bar (void);
+
+void
+foo (int x)
+{
+  __builtin_unwind_init ();
+  __builtin_eh_return (x, bar);
+}
+/* { dg-final { scan-assembler "\\\$16" } } */
+/* { dg-final { scan-assembler "\\\$17" } } */
+/* { dg-final { scan-assembler "\\\$18" } } */
+/* { dg-final { scan-assembler "\\\$19" } } */
+/* { dg-final { scan-assembler "\\\$20" } } */
+/* { dg-final { scan-assembler "\\\$21" } } */
+/* { dg-final { scan-assembler "\\\$22" } } */
+/* { dg-final { scan-assembler "\\\$23" } } */
+/* { dg-final { scan-assembler "\\\$(30|fp)" } } */
+/* { dg-final { scan-assembler "\\\$f20" } } */
+/* { dg-final { scan-assembler "\\\$f22" } } */
+/* { dg-final { scan-assembler "\\\$f24" } } */
+/* { dg-final { scan-assembler "\\\$f26" } } */
+/* { dg-final { scan-assembler "\\\$f28" } } */
+/* { dg-final { scan-assembler "\\\$f30" } } */
+/* { dg-final { scan-assembler-not "\\\$f21" } } */
+/* { dg-final { scan-assembler-not "\\\$f23" } } */
+/* { dg-final { scan-assembler-not "\\\$f25" } } */
+/* { dg-final { scan-assembler-not "\\\$f27" } } */
+/* { dg-final { scan-assembler-not "\\\$f29" } } */
+/* { dg-final { scan-assembler-not "\\\$f31" } } */
diff --git a/gcc/testsuite/gcc.target/mips/mips.exp b/gcc/testsuite/gcc.target/mips/mips.exp
index e2b137e..a9beb27 100644
--- a/gcc/testsuite/gcc.target/mips/mips.exp
+++ b/gcc/testsuite/gcc.target/mips/mips.exp
@@ -235,7 +235,7 @@ set mips_option_groups {
     endianness "-E(L|B)|-me(l|b)"
     float "-m(hard|soft)-float"
     forbid_cpu "forbid_cpu=.*"
-    fp "-mfp(32|64)"
+    fp "-mfp(32|xx|64)"
     gp "-mgp(32|64)"
     long "-mlong(32|64)"
     micromips "-mmicromips|-mno-micromips"
@@ -248,6 +248,10 @@ set mips_option_groups {
     dump "-fdump-.*"
 }
 
+for { set option 0 } { $option < 32 } { incr option } {
+    lappend mips_option_groups "fixed-f$option" "-ffixed-f$option"
+}
+
 # Add -mfoo/-mno-foo options to mips_option_groups.
 foreach option {
     abicalls
@@ -270,6 +274,7 @@ foreach option {
     synci
     relax-pic-calls
     mcount-ra-address
+    odd-spreg
 } {
     lappend mips_option_groups $option "-m(no-|)$option"
 }
@@ -723,8 +728,12 @@ proc mips-dg-init {} {
 	    #if __mips_fpr == 64
 	    "-mfp64",
 	    #else
+	    #if __mips_fpr == 0
+	    "-mfpxx",
+	    #else
 	    "-mfp32",
 	    #endif
+	    #endif
 
 	    #ifdef __mips64
 	    "-mgp64",
@@ -756,6 +765,12 @@ proc mips-dg-init {} {
 	    "-mno-paired-single",
 	    #endif
 
+	    #if _MIPS_SPFPSET == 32
+	    "-modd-spreg",
+	    #else
+	    "-mno-odd-spreg",
+	    #endif
+
 	    #if __mips_abicalls
 	    "-mabicalls",
 	    #else
@@ -841,6 +856,8 @@ proc mips-dg-finish {} {
 #            |                           |
 #         -mfp64                      -mfp32
 #            |                           |
+#         -modd-spreg                 -mno-odd-spreg
+#            |                           |
 #         -mabs=2008/-mabs=legacy     <no option>
 #            |                           |
 #         -mhard-float                -msoft-float
@@ -930,6 +947,7 @@ proc mips-dg-options { args } {
     mips_option_dependency options "-mips3d" "-mpaired-single"
     mips_option_dependency options "-mpaired-single" "-mfp64"
     mips_option_dependency options "-mfp64" "-mhard-float"
+    mips_option_dependency options "-mfp64" "-modd-spreg"
     mips_option_dependency options "-mabs=2008" "-mhard-float"
     mips_option_dependency options "-mabs=legacy" "-mhard-float"
     mips_option_dependency options "-mrelax-pic-calls" "-mno-plt"
@@ -1046,10 +1064,13 @@ proc mips-dg-options { args } {
         # We need a MIPS32 or MIPS64 ISA for:
 	#
         #   - paired-single instructions(*)
+        #   - odd numbered single precision registers
         #
 	# (*) Note that we don't support MIPS V at the moment.
 	} elseif { $isa_rev < 1
-		   && [mips_have_test_option_p options "-mpaired-single"] } {
+		   && ([mips_have_test_option_p options "-mpaired-single"]
+		       || ([mips_have_test_option_p options "-modd-spreg"]
+			   && ![mips_have_test_option_p options "-mfp64"]))} {
 	    if { $gp_size == 32 } {
 		mips_make_test_option options "-mips32"
 	    } else {
@@ -1071,7 +1092,9 @@ proc mips-dg-options { args } {
 	# (*) needed by both -mbranch-likely and -mfix-r10000
 	} elseif { $isa < 2
 		   && ([mips_have_test_option_p options "-mbranch-likely"]
-		       || [mips_have_test_option_p options "-mfix-r10000"]) } {
+		       || [mips_have_test_option_p options "-mfix-r10000"]
+		       || ($gp_size == 32
+			   && [mips_have_test_option_p options "-mfpxx"])) } {
 	    mips_make_test_option options "-mips2"
 	# Check whether we need to switch from a 32-bit processor to the
 	# "nearest" 64-bit processor.
@@ -1122,6 +1145,9 @@ proc mips-dg-options { args } {
 	} elseif { [mips_have_test_option_p options "-mlong64"]
 		   && [mips_long32_abi_p $abi] } {
 	    set force_abi 1
+	} elseif { [mips_have_test_option_p options "-mfpxx"]
+		   && ![mips_same_option_p $abi "-mabi=32"] } {
+	    set force_abi 1
 	} else {
 	    set force_abi 0
 	}
@@ -1193,6 +1219,9 @@ proc mips-dg-options { args } {
 	}
 	if { $isa_rev < 1 } {
 	    mips_make_test_option options "-mno-paired-single"
+	    if { ![mips_have_test_option_p options "-mgp64"] } {
+		mips_make_test_option options "-mno-odd-spreg"
+	    }
 	}
 	if { $isa_rev < 2 } {
 	    if { $gp_size == 32 } {
@@ -1223,6 +1252,7 @@ proc mips-dg-options { args } {
     mips_option_dependency options "-mplt" "-mno-shared"
     mips_option_dependency options "-mno-shared" "-fno-pic"
     mips_option_dependency options "-mfp32" "-mno-paired-single"
+    mips_option_dependency options "-mfpxx" "-mno-paired-single"
     mips_option_dependency options "-msoft-float" "-mno-paired-single"
     mips_option_dependency options "-mno-paired-single" "-mno-mips3d"
 
@@ -1244,7 +1274,9 @@ proc mips-dg-options { args } {
     foreach group $mips_abi_groups {
 	set old_option [mips_original_option $group]
 	set new_option [mips_option options $group]
-	if { ![mips_same_option_p $old_option $new_option] } {
+	if { ![mips_same_option_p $old_option $new_option]
+	     && ![mips_same_option_p $old_option "-mfpxx"]
+	     && ![mips_same_option_p $new_option "-mfpxx"] } {
 	    switch -- [lindex $do_what 0] {
 		link -
 		run {
diff --git a/gcc/testsuite/gcc.target/mips/movdf-1.c b/gcc/testsuite/gcc.target/mips/movdf-1.c
new file mode 100644
index 0000000..f0267d0
--- /dev/null
+++ b/gcc/testsuite/gcc.target/mips/movdf-1.c
@@ -0,0 +1,14 @@
+/* Check that we move DFmode values via memory between FP and GP.  */
+/* { dg-skip-if "code quality test" { *-*-* } { "-O0" } { "" } } */
+/* { dg-options "-mabi=32 -mfpxx isa=2" } */
+
+void bar (void);
+
+double
+foo (int x, double a)
+{
+  return a;
+}
+/* { dg-final { scan-assembler-not "mthc1" } } */
+/* { dg-final { scan-assembler-not "mtc1" } } */
+/* { dg-final { scan-assembler-times "ldc1" 1 } } */
diff --git a/gcc/testsuite/gcc.target/mips/movdf-2.c b/gcc/testsuite/gcc.target/mips/movdf-2.c
new file mode 100644
index 0000000..175b61c
--- /dev/null
+++ b/gcc/testsuite/gcc.target/mips/movdf-2.c
@@ -0,0 +1,14 @@
+/* Check that we move DFmode values using mthc between FP and GP.  */
+/* { dg-skip-if "code quality test" { *-*-* } { "-O0" } { "" } } */
+/* { dg-options "-mabi=32 -mfpxx isa_rev=2" } */
+
+void bar (void);
+
+double
+foo (int x, double a)
+{
+  return a;
+}
+/* { dg-final { scan-assembler "mthc1" } } */
+/* { dg-final { scan-assembler "mtc1" } } */
+/* { dg-final { scan-assembler-not "ldc1" } } */
diff --git a/gcc/testsuite/gcc.target/mips/movdf-3.c b/gcc/testsuite/gcc.target/mips/movdf-3.c
new file mode 100644
index 0000000..5db52c9
--- /dev/null
+++ b/gcc/testsuite/gcc.target/mips/movdf-3.c
@@ -0,0 +1,13 @@
+/* Check that we move DFmode values using mtc1 between FP and GP.  */
+/* { dg-skip-if "code quality test" { *-*-* } { "-O0" } { "" } } */
+/* { dg-options "-mabi=32 -mfp32 isa=2" } */
+
+void bar (void);
+
+double
+foo (int x, double a)
+{
+  return a;
+}
+/* { dg-final { scan-assembler-times "mtc1" 2 } } */
+/* { dg-final { scan-assembler-not "ldc1" } } */
diff --git a/gcc/testsuite/gcc.target/mips/oddspreg-1.c b/gcc/testsuite/gcc.target/mips/oddspreg-1.c
new file mode 100644
index 0000000..a9c6957
--- /dev/null
+++ b/gcc/testsuite/gcc.target/mips/oddspreg-1.c
@@ -0,0 +1,13 @@
+/* Check that we enable odd-numbered single precision registers.  */
+/* { dg-options "-mabi=32 -modd-spreg -mhard-float" } */
+
+#if _MIPS_SPFPSET != 32
+#error "Incorrect number of single-precision registers reported"
+#endif
+
+void
+foo ()
+{
+  register float foo asm ("$f1");
+  asm volatile ("" : "=f" (foo));
+}
diff --git a/gcc/testsuite/gcc.target/mips/oddspreg-2.c b/gcc/testsuite/gcc.target/mips/oddspreg-2.c
new file mode 100644
index 0000000..e2e0a26
--- /dev/null
+++ b/gcc/testsuite/gcc.target/mips/oddspreg-2.c
@@ -0,0 +1,10 @@
+/* Check that we disable odd-numbered single precision registers.  */
+/* { dg-skip-if "needs asm output" { *-*-* } { "-fno-fat-lto-objects" } { "" } } */
+/* { dg-options "-mabi=32 -mno-odd-spreg -mhard-float" } */
+
+void
+foo ()
+{
+  register float foo asm ("$f1"); /* { dg-error "isn't suitable for" } */
+  asm volatile ("" : "=f" (foo));
+}
diff --git a/gcc/testsuite/gcc.target/mips/oddspreg-3.c b/gcc/testsuite/gcc.target/mips/oddspreg-3.c
new file mode 100644
index 0000000..8a2eb63
--- /dev/null
+++ b/gcc/testsuite/gcc.target/mips/oddspreg-3.c
@@ -0,0 +1,10 @@
+/* Check that we disable odd-numbered single precision registers.  */
+/* { dg-skip-if "needs asm output" { *-*-* } { "-fno-fat-lto-objects" } { "" } } */
+/* { dg-options "-mabi=32 -march=loongson3a -mhard-float" } */
+
+void
+foo ()
+{
+  register float foo asm ("$f1"); /* { dg-error "isn't suitable for" } */
+  asm volatile ("" : "=f" (foo));
+}
diff --git a/gcc/testsuite/gcc.target/mips/oddspreg-4.c b/gcc/testsuite/gcc.target/mips/oddspreg-4.c
new file mode 100644
index 0000000..723424a
--- /dev/null
+++ b/gcc/testsuite/gcc.target/mips/oddspreg-4.c
@@ -0,0 +1,15 @@
+/* Check that we disable odd-numbered single precision registers and can
+   still generate code.  */
+/* { dg-options "-mabi=32 -mno-odd-spreg -mhard-float" } */
+
+#if _MIPS_SPFPSET != 16
+#error "Incorrect number of single-precision registers reported"
+#endif
+
+float a;
+float
+foo ()
+{
+  float b = a + 1.0f;
+  return b;
+}
diff --git a/gcc/testsuite/gcc.target/mips/oddspreg-5.c b/gcc/testsuite/gcc.target/mips/oddspreg-5.c
new file mode 100644
index 0000000..8d7d884
--- /dev/null
+++ b/gcc/testsuite/gcc.target/mips/oddspreg-5.c
@@ -0,0 +1,11 @@
+/* Check that -mno-odd-spreg is not supported with -mabi=64.  */
+/* { dg-options "-mabi=64 -mno-odd-spreg -mhard-float" } */
+/* { dg-error "unsupported combination" "" { target *-*-* } 0 } */
+
+float a;
+float
+foo ()
+{
+  float b = a + 1.0f;
+  return b;
+}
diff --git a/gcc/testsuite/gcc.target/mips/oddspreg-6.c b/gcc/testsuite/gcc.target/mips/oddspreg-6.c
new file mode 100644
index 0000000..955dea901
--- /dev/null
+++ b/gcc/testsuite/gcc.target/mips/oddspreg-6.c
@@ -0,0 +1,10 @@
+/* Check that we disable odd-numbered single precision registers for FPXX.  */
+/* { dg-skip-if "needs asm output" { *-*-* } { "-fno-fat-lto-objects" } { "" } } */
+/* { dg-options "-mabi=32 -mfpxx -mhard-float" } */
+
+void
+foo ()
+{
+  register float foo asm ("$f1"); /* { dg-error "isn't suitable for" } */
+  asm volatile ("" : "=f" (foo));
+}
diff --git a/libgcc/config/mips/mips16.S b/libgcc/config/mips/mips16.S
index dde8939..c0c73ff 100644
--- a/libgcc/config/mips/mips16.S
+++ b/libgcc/config/mips/mips16.S
@@ -21,6 +21,8 @@ a copy of the GCC Runtime Library Exception along with this program;
 see the files COPYING3 and COPYING.RUNTIME respectively.  If not, see
 <http://www.gnu.org/licenses/>.  */
 
+#include "auto-host.h"
+
 #if defined(__mips_micromips) || defined(__mips_soft_float)
   /* Do nothing because this code is only needed when linking
      against mips16 hard-float objects.  Neither micromips code
@@ -29,6 +31,16 @@ see the files COPYING3 and COPYING.RUNTIME respectively.  If not, see
      for those cases.  */
 #else
 
+#if defined(HAVE_AS_MODULE)
+#if __mips_fpr == 32
+	.module fp=32
+#elif __mips_fpr == 0
+	.module fp=xx
+#elif __mips_fpr == 64
+	.module fp=64
+#endif
+#endif
+
 /* This file contains mips16 floating point support functions.  These
    functions are called by mips16 code to handle floating point when
    -msoft-float is not used.  They accept the arguments and return
@@ -152,8 +164,6 @@ see the files COPYING3 and COPYING.RUNTIME respectively.  If not, see
 /* The high 32 bits of $2 correspond to the second word in memory;
    i.e. the imaginary part.  */
 #define MOVE_SC_RET(D, T) MERGE_GPR##D ($2, $f1, $f0); jr T
-#elif __mips_fpr == 64
-#define MOVE_SC_RET(D, T) m##D##c1 $2,$f0; DELAY##D (T, m##D##c1 $3,$f1)
 #else
 #define MOVE_SC_RET(D, T) m##D##c1 $2,$f0; DELAY##D (T, m##D##c1 $3,$f2)
 #endif
@@ -174,16 +184,29 @@ see the files COPYING3 and COPYING.RUNTIME respectively.  If not, see
 #define MOVE_DF_BYTE8(D) dm##D##c1 $5,$f13
 #define MOVE_DF_RET(D, T) DELAY##D (T, dm##D##c1 $2,$f0)
 #define MOVE_DC_RET(D, T) dm##D##c1 $3,$f1; MOVE_DF_RET (D, T)
-#elif __mips_fpr == 64 && defined(__MIPSEB__)
+#elif __mips_fpr != 32 && __mips_isa_rev >= 2 && defined(__MIPSEB__)
 #define MOVE_DF_BYTE0(D) m##D##c1 $5,$f12; m##D##hc1 $4,$f12
 #define MOVE_DF_BYTE8(D) m##D##c1 $7,$f14; m##D##hc1 $6,$f14
 #define MOVE_DF_RET(D, T) m##D##c1 $3,$f0; DELAY##D (T, m##D##hc1 $2,$f0)
-#define MOVE_DC_RET(D, T) m##D##c1 $5,$f1; m##D##hc1 $4,$f1; MOVE_DF_RET (D, T)
-#elif __mips_fpr == 64
+#define MOVE_DC_RET(D, T) m##D##c1 $5,$f2; m##D##hc1 $4,$f2; MOVE_DF_RET (D, T)
+#elif __mips_fpr != 32 && __mips_isa_rev >= 2
 #define MOVE_DF_BYTE0(D) m##D##c1 $4,$f12; m##D##hc1 $5,$f12
 #define MOVE_DF_BYTE8(D) m##D##c1 $6,$f14; m##D##hc1 $7,$f14
 #define MOVE_DF_RET(D, T) m##D##c1 $2,$f0; DELAY##D (T, m##D##hc1 $3,$f0)
-#define MOVE_DC_RET(D, T) m##D##c1 $4,$f1; m##D##hc1 $5,$f1; MOVE_DF_RET (D, T)
+#define MOVE_DC_RET(D, T) m##D##c1 $4,$f2; m##D##hc1 $5,$f2; MOVE_DF_RET (D, T)
+#elif __mips_fpr == 0
+#define MOVE_DF_BYTE0t sw $4, 0($29); sw $5, 4($29); ldc1 $f12, 0($29)
+#define MOVE_DF_BYTE0f sdc1 $f12, 0($29); lw $4, 0($29); lw $5, 4($29)
+#define MOVE_DF_BYTE0(D) MOVE_DF_BYTE0##D
+#define MOVE_DF_BYTE8t sw $6, 8($29); sw $7, 12($29); ldc1 $f14, 8($29)
+#define MOVE_DF_BYTE8f sdc1 $f14, 8($29); lw $6, 8($29); lw $7, 12($29)
+#define MOVE_DF_BYTE8(D) MOVE_DF_BYTE8##D
+#define MOVE_DF_RETt(T) sw $2, 0($29); sw $3, 4($29); DELAYt (T, ldc1 $f0, 0($29))
+#define MOVE_DF_RETf(T) sdc1 $f0, 0($29); lw $2, 0($29); DELAYf (T, lw $3, 4($29))
+#define MOVE_DF_RET(D, T) MOVE_DF_RET##D(T)
+#define MOVE_DC_RETt(T) sw $4, 8($29); sw $5, 12($29); ldc1 $f2, 8($29); MOVE_DF_RETt(T)
+#define MOVE_DC_RETf(T) sdc1 $f2, 8($29); lw $4, 8($29); lw $5, 12($29); MOVE_DF_RETf(T)
+#define MOVE_DC_RET(D, T) MOVE_DF_RET##D(T)
 #elif defined(__MIPSEB__)
 /* FPRs are little-endian.  */
 #define MOVE_DF_BYTE0(D) m##D##c1 $4,$f13; m##D##c1 $5,$f12
-- 
1.9.4

