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