The MIPS back end forces every non-zero floating-point constant into the
constant pool, because mips_const_insns returns 0 for CONST_DOUBLE.  On a
target with a small data cache, that is a poor trade: a pool entry adds
data-cache pressure and a load, when the value can instead be built in a
GPR and moved to the FPU with mtc1 (as the old EGCS compiler did).

Build such immediates inline:

  - mips_loadable_fp_const_p recognises the constants worth synthesising:
    every non-zero SFmode value (at most LUI+ORI, then MTC1), and DFmode
    values whose 64-bit pattern builds in at most two integer instructions.
    Arbitrary doubles stay in the pool so the instruction stream is not
    bloated.

  - mips_cannot_force_const_mem returns true for them, so emit_move_insn
    hands the constant to the move expander rather than the pool.

  - mips_legitimize_move synthesises it: a memory destination receives the
    raw integer bits directly (e.g. SW, never touching the FPU); a register
    destination is loaded by the new *load_const<mode> insn.

  - *load_const<mode> is intentionally a single insn that carries an integer
    scratch.  The scratch and the mtc1 transfer are exposed only after reload
    (mips_split_load_fp_const).  Presenting one insn to the register allocator
    matters: an exposed pre-reload GPR temporary perturbs IRA's equivalence
    analysis and makes it spill neighbouring values.

For the original report's testcase the constant pool and the stack frame both
disappear: 5.0f becomes LUI+MTC1, and the 10.0/60.0 stores become plain SW.

The change only affects the lowering of non-zero FP CONST_DOUBLE moves; all
other code is unaffected.  Cross-built for mips64-elf; libgcc rebuilds
cleanly.  Code generation was checked for SFmode and DFmode across -mgp32 and
-mgp64, -mfp32 and -mfp64, both endiannesses, hard/soft/single float and
MIPS16, and at -O0..-O3/-Os; the synthesised bit patterns were verified to be
bit-identical to the IEEE-754 values they replace.

gcc/ChangeLog:

        * config/mips/mips.cc (mips_loadable_fp_const_p): New function.
        (mips_emit_fp_const): New function.
        (mips_split_load_fp_const): New function.
        (mips_cannot_force_const_mem): Keep loadable FP immediates out of
        the constant pool.
        (mips_legitimize_move): Synthesise loadable FP immediates inline.
        * config/mips/mips-protos.h (mips_loadable_fp_const_p): Declare.
        (mips_split_load_fp_const): Declare.
        * config/mips/mips.md (fpconstint): New mode attribute.
        (fpconstlen): Likewise.
        (*load_const<mode>): New define_insn_and_split.

gcc/testsuite/ChangeLog:

        * gcc.target/mips/fp-const-no-pool.c: New test.

Signed-off-by: ytlee <[email protected]>
---
 gcc/config/mips/mips-protos.h                 |   2 +
 gcc/config/mips/mips.cc                       | 148 ++++++++++++++++++
 gcc/config/mips/mips.md                       |  23 +++
 .../gcc.target/mips/fp-const-no-pool.c        |  25 +++
 4 files changed, 198 insertions(+)
 create mode 100644 gcc/testsuite/gcc.target/mips/fp-const-no-pool.c

diff --git a/gcc/config/mips/mips-protos.h b/gcc/config/mips/mips-protos.h
index 472baac8fde..1887e6e8265 100644
--- a/gcc/config/mips/mips-protos.h
+++ b/gcc/config/mips/mips-protos.h
@@ -210,6 +210,8 @@ extern bool mips_split_symbol (rtx, rtx, machine_mode, rtx 
*);
 extern rtx mips_unspec_address (rtx, enum mips_symbol_type);
 extern rtx mips_strip_unspec_address (rtx);
 extern void mips_move_integer (rtx, rtx, unsigned HOST_WIDE_INT);
+extern bool mips_loadable_fp_const_p (machine_mode, rtx);
+extern void mips_split_load_fp_const (rtx, rtx, rtx);
 extern bool mips_legitimize_move (machine_mode, rtx, rtx);
 
 extern rtx mips_subword (rtx, bool);
diff --git a/gcc/config/mips/mips.cc b/gcc/config/mips/mips.cc
index 303766db522..48b277d1296 100644
--- a/gcc/config/mips/mips.cc
+++ b/gcc/config/mips/mips.cc
@@ -2551,6 +2551,141 @@ mips_symbol_insns (enum mips_symbol_type type, 
machine_mode mode)
   return mips_symbol_insns_1 (type, mode) * (TARGET_MIPS16 ? 2 : 1);
 }
 
+/* Return true if X is a floating-point CONST_DOUBLE that we want to
+   synthesise in integer registers (and transfer to the FPU with mtc1)
+   instead of loading from the constant pool.  Keeping immediates out
+   of memory avoids data-cache pressure; for SFmode the inline sequence
+   is also no longer than a pool load.  The actual expansion is done by
+   mips_emit_fp_const / mips_legitimize_move.  */
+
+bool
+mips_loadable_fp_const_p (machine_mode mode, rtx x)
+{
+  if (x == NULL_RTX || GET_CODE (x) != CONST_DOUBLE || x == CONST0_RTX (mode))
+    return false;
+
+  /* Single precision: at most LUI(+ORI) then MTC1; always worthwhile.  */
+  if (mode == SFmode)
+    return true;
+
+  /* Double precision: only inline when the 64-bit pattern is cheap to build,
+     so that arbitrary doubles do not bloat the instruction stream.  */
+  if (mode == DFmode && TARGET_DOUBLE_FLOAT)
+    {
+      struct mips_integer_op codes[MIPS_MAX_INTEGER_OPS];
+      rtx bits = simplify_gen_subreg (DImode, x, DFmode, 0);
+      unsigned int cost;
+
+      if (bits == NULL_RTX || !CONST_INT_P (bits))
+       return false;
+      if (TARGET_64BIT)
+       cost = mips_build_integer (codes, UINTVAL (bits));
+      else
+       {
+         unsigned HOST_WIDE_INT hi = (UINTVAL (bits) >> 32) & 0xffffffff;
+         unsigned HOST_WIDE_INT lo = UINTVAL (bits) & 0xffffffff;
+         cost = (lo ? mips_build_integer (codes, lo) : 0)
+                + (hi ? mips_build_integer (codes, hi) : 0);
+       }
+      /* Each word is at most one LUI/ORI/ADDIU; cap the total at two.  */
+      return cost > 0 && cost <= 2;
+    }
+
+  return false;
+}
+
+/* Move floating-point constant SRC (satisfying mips_loadable_fp_const_p)
+   into DEST without using the constant pool.  Return true on success.
+
+   A memory destination is given the raw integer bits directly (e.g. SW),
+   keeping the FPU out of the picture.  A hard-float register destination is
+   handled by emitting a single "*load_const" insn that holds an integer
+   scratch; the scratch and the mtc1 transfer are only exposed after reload
+   (see mips_split_load_fp_const).  Presenting one insn to the register
+   allocator is important: an exposed pre-reload GPR temporary perturbs IRA's
+   equivalence analysis and makes it spill neighbouring values.  Soft-float
+   keeps the value in GPRs, so the pattern is simply built there.  */
+
+static bool
+mips_emit_fp_const (machine_mode mode, rtx dest, rtx src)
+{
+  machine_mode imode = (GET_MODE_SIZE (mode) == 8 ? DImode : SImode);
+
+  /* Store the raw integer bits directly, bypassing the FPU entirely.  Skip
+     volatile references so that a multiword store keeps its original width and
+     access count; those go through the register path instead.  */
+  if (MEM_P (dest) && !MEM_VOLATILE_P (dest))
+    {
+      rtx bits = simplify_gen_subreg (imode, src, mode, 0);
+      if (bits == NULL_RTX || !CONST_SCALAR_INT_P (bits))
+       return false;
+      mips_emit_move (adjust_address (dest, imode, 0), bits);
+      return true;
+    }
+
+  if (!REG_P (dest) && GET_CODE (dest) != SUBREG)
+    return false;
+
+  /* Hard float: present a single insn to the register allocator; the integer
+     scratch and the mtc1 transfer are exposed only after reload, by
+     mips_split_load_fp_const.  */
+  if (TARGET_HARD_FLOAT)
+    {
+      rtx scratch = gen_rtx_SCRATCH (imode);
+      emit_insn (gen_rtx_PARALLEL (VOIDmode,
+                                  gen_rtvec (2,
+                                             gen_rtx_SET (dest, src),
+                                             gen_rtx_CLOBBER (VOIDmode,
+                                                              scratch))));
+      return true;
+    }
+
+  /* Soft float: the value lives in GPRs, so just build it there.  */
+  {
+    rtx bits = simplify_gen_subreg (imode, src, mode, 0);
+    rtx ireg;
+    if (bits == NULL_RTX || !CONST_SCALAR_INT_P (bits))
+      return false;
+    ireg = gen_reg_rtx (imode);
+    mips_emit_move (ireg, bits);
+    mips_emit_move (dest, gen_lowpart (mode, ireg));
+    return true;
+  }
+}
+
+/* Split the "*load_const" insn after reload: materialise floating-point
+   constant CST in integer registers and move it into register DEST.  For a
+   hard-float (FPR) destination the bits are built in the integer SCRATCH and
+   transferred with mtc1; for a GPR destination they are built in place.  */
+
+void
+mips_split_load_fp_const (rtx dest, rtx cst, rtx scratch)
+{
+  machine_mode mode = GET_MODE (dest);
+  machine_mode imode = (GET_MODE_SIZE (mode) == 8 ? DImode : SImode);
+  rtx bits = simplify_gen_subreg (imode, cst, mode, 0);
+  bool gpr_dest = !FP_REG_P (REGNO (dest));
+  rtx itmp = gpr_dest ? gen_lowpart (imode, dest) : scratch;
+
+  /* Under -mgp32 a DImode value is two 32-bit words; build each separately.  
*/
+  if (imode == DImode && !TARGET_64BIT)
+    {
+      mips_move_integer (mips_subword (itmp, false),
+                        mips_subword (itmp, false),
+                        INTVAL (mips_subword (bits, false)));
+      mips_move_integer (mips_subword (itmp, true),
+                        mips_subword (itmp, true),
+                        INTVAL (mips_subword (bits, true)));
+    }
+  else
+    mips_move_integer (itmp, itmp, INTVAL (bits));
+
+  /* FPR destination: move the integer pattern in with mtc1 (DF on -mgp32 is
+     split again into two transfers).  */
+  if (!gpr_dest)
+    mips_emit_move (dest, gen_lowpart (mode, scratch));
+}
+
 /* Implement TARGET_CANNOT_FORCE_CONST_MEM.  */
 
 static bool
@@ -2564,6 +2699,11 @@ mips_cannot_force_const_mem (machine_mode mode, rtx x)
   if (GET_CODE (x) == HIGH)
     return true;
 
+  /* Small FP immediates are built inline by mips_legitimize_move instead of
+     being placed in the constant pool.  */
+  if (mips_loadable_fp_const_p (mode, x))
+    return true;
+
   /* As an optimization, reject constants that mips_legitimize_move
      can expand inline.
 
@@ -3904,6 +4044,14 @@ mips_legitimize_const_move (machine_mode mode, rtx dest, 
rtx src)
 bool
 mips_legitimize_move (machine_mode mode, rtx dest, rtx src)
 {
+  /* Synthesise FP immediates with integer loads + mtc1 (or a direct integer
+     store) instead of loading them from the constant pool.  This needs an
+     integer temporary, so only do it while we can still create pseudos.  */
+  if (can_create_pseudo_p ()
+      && mips_loadable_fp_const_p (mode, src)
+      && mips_emit_fp_const (mode, dest, src))
+    return true;
+
   /* Both src and dest are non-registers;  one special case is supported where
      the source is (const_int 0) and the store can source the zero register.
      MIPS16 and MSA are never able to source the zero register directly in
diff --git a/gcc/config/mips/mips.md b/gcc/config/mips/mips.md
index 18244e4abcc..f57e6947da7 100644
--- a/gcc/config/mips/mips.md
+++ b/gcc/config/mips/mips.md
@@ -5364,6 +5364,29 @@
   [(set_attr "move_type" "move,move,move,load,store")
    (set_attr "mode" "DF")])
 
+;; Load a floating-point immediate without using the constant pool.
+;; It is presented to the register allocator as a single insn;
+;; the integer build and the mtc1 transfer are exposed only after reload
+;; (see mips_split_load_fp_const).  Emitted by mips_emit_fp_const; the integer
+;; scratch is only needed for an FPR destination.
+(define_mode_attr fpconstint [(SF "SI") (DF "DI")])
+(define_mode_attr fpconstlen [(SF "12") (DF "24")])
+
+(define_insn_and_split "*load_const<mode>"
+  [(set (match_operand:SCALARF 0 "register_operand" "=f,d")
+       (match_operand:SCALARF 1 "const_double_operand" "E,E"))
+   (clobber (match_scratch:<fpconstint> 2 "=&d,X"))]
+  "TARGET_HARD_FLOAT && mips_loadable_fp_const_p (<MODE>mode, operands[1])"
+  "#"
+  "&& reload_completed"
+  [(const_int 0)]
+{
+  mips_split_load_fp_const (operands[0], operands[1], operands[2]);
+  DONE;
+}
+  [(set_attr "mode" "<MODE>")
+   (set_attr "length" "<fpconstlen>")])
+
 ;; 128-bit integer moves
 
 (define_expand "movti"
diff --git a/gcc/testsuite/gcc.target/mips/fp-const-no-pool.c 
b/gcc/testsuite/gcc.target/mips/fp-const-no-pool.c
new file mode 100644
index 00000000000..aeb1bcc91e8
--- /dev/null
+++ b/gcc/testsuite/gcc.target/mips/fp-const-no-pool.c
@@ -0,0 +1,25 @@
+/* Verify that small floating-point immediates are synthesised in registers
+   (and transferred to the FPU with mtc1) or stored directly as integers,
+   rather than being loaded from the constant pool.  */
+/* { dg-options "-mhard-float" } */
+/* { dg-skip-if "code quality test" { *-*-* } { "-O0" } { "" } } */
+
+NOMIPS16 float
+add5 (float x)
+{
+  return x + 5.0f;
+}
+
+NOMIPS16 void
+store10 (float *p)
+{
+  *p = 10.0f;
+}
+
+/* The 5.0f operand must reach the FPU through a GPR (mtc1), not a load.  */
+/* { dg-final { scan-assembler "\tmtc1\t" } } */
+/* The store must use a plain integer store, bypassing the FPU.  */
+/* { dg-final { scan-assembler "\tsw\t" } } */
+/* Nothing should be loaded from a floating-point constant pool.  */
+/* { dg-final { scan-assembler-not "\tlwc1\t" } } */
+/* { dg-final { scan-assembler-not "\\\$LC" } } */
-- 
2.51.0

Reply via email to