[RFC] aarch64: Split selected LDPs to improve store forwarding

huzife <[email protected]>
Newsgroups gmane.comp.gcc.patches
Message-ID <[email protected]>
A load pair that partially overlaps a recent scalar store can prevent the
load from using store-to-load forwarding.  Splitting the pair lets the
overlapping half use the forwarding path.

Add a late AArch64 RTL pass that handles non-writeback SI and DI GPR load
pairs.  Search a bounded part of the containing basic block for a scalar
store to either half, and replace the pair only when both scalar loads are
recognized.  Reject volatile, frame-related and exception-sensitive forms,
and disable the transformation for size optimization.  Keep the pass off by
default behind a target parameter while its profitability is evaluated.

gcc/ChangeLog:

	* config.gcc (aarch64*-*-*): Add aarch64-ldp-split.o.
	* config/aarch64/aarch64-ldp-split.cc: New file.
	* config/aarch64/aarch64-passes.def: Insert pass_split_ldp_stlf before
	pass_sched2.
	* config/aarch64/aarch64-protos.h (make_pass_split_ldp_stlf): Declare.
	* config/aarch64/aarch64.opt: Add aarch64-split-ldp-stlf parameter.
	* config/aarch64/t-aarch64 (aarch64-ldp-split.o): New rule.
	* doc/params.texi (aarch64-split-ldp-stlf): Document.

gcc/testsuite/ChangeLog:

	* gcc.target/aarch64/split-ldp-stlf-1.c: New test.
	* gcc.target/aarch64/split-ldp-stlf-2.c: New test.
	* gcc.target/aarch64/split-ldp-stlf-3.c: New test.
	* gcc.target/aarch64/split-ldp-stlf-4.c: New test.
	* gcc.target/aarch64/split-ldp-stlf-5.c: New test.
	* gcc.target/aarch64/split-ldp-stlf-6.c: New test.

Signed-off-by: huzife <[email protected]>
---
 gcc/config.gcc                                |   1 +
 gcc/config/aarch64/aarch64-ldp-split.cc       | 316 ++++++++++++++++++
 gcc/config/aarch64/aarch64-passes.def         |   1 +
 gcc/config/aarch64/aarch64-protos.h           |   1 +
 gcc/config/aarch64/aarch64.opt                |   5 +
 gcc/config/aarch64/t-aarch64                  |   6 +
 gcc/doc/params.texi                           |   8 +
 .../gcc.target/aarch64/split-ldp-stlf-1.c     |  43 +++
 .../gcc.target/aarch64/split-ldp-stlf-2.c     |  13 +
 .../gcc.target/aarch64/split-ldp-stlf-3.c     |  22 ++
 .../gcc.target/aarch64/split-ldp-stlf-4.c     |  14 +
 .../gcc.target/aarch64/split-ldp-stlf-5.c     |  13 +
 .../gcc.target/aarch64/split-ldp-stlf-6.c     |  13 +
 13 files changed, 456 insertions(+)
 create mode 100644 gcc/config/aarch64/aarch64-ldp-split.cc
 create mode 100644 gcc/testsuite/gcc.target/aarch64/split-ldp-stlf-1.c
 create mode 100644 gcc/testsuite/gcc.target/aarch64/split-ldp-stlf-2.c
 create mode 100644 gcc/testsuite/gcc.target/aarch64/split-ldp-stlf-3.c
 create mode 100644 gcc/testsuite/gcc.target/aarch64/split-ldp-stlf-4.c
 create mode 100644 gcc/testsuite/gcc.target/aarch64/split-ldp-stlf-5.c
 create mode 100644 gcc/testsuite/gcc.target/aarch64/split-ldp-stlf-6.c

diff --git a/gcc/config.gcc b/gcc/config.gcc
index 0477bf22c8a..00aa965f162 100644
--- a/gcc/config.gcc
+++ b/gcc/config.gcc
@@ -362,6 +362,7 @@ aarch64*-*-*)
 	extra_objs="${extra_objs} aarch-bti-insert.o"
 	extra_objs="${extra_objs} aarch64-early-ra.o"
 	extra_objs="${extra_objs} aarch64-ldp-fusion.o"
+	extra_objs="${extra_objs} aarch64-ldp-split.o"
 	extra_objs="${extra_objs} aarch64-sched-dispatch.o"
 	extra_objs="${extra_objs} aarch64-json-tunings-printer.o"
 	extra_objs="${extra_objs} aarch64-json-tunings-parser.o"
diff --git a/gcc/config/aarch64/aarch64-ldp-split.cc b/gcc/config/aarch64/aarch64-ldp-split.cc
new file mode 100644
index 00000000000..a4c2c159529
--- /dev/null
+++ b/gcc/config/aarch64/aarch64-ldp-split.cc
@@ -0,0 +1,316 @@
+/* Split selected load pairs to improve store-to-load forwarding on AArch64.
+   Copyright (C) 2026 Free Software Foundation, Inc.
+
+   This file is part of GCC.
+
+   GCC is free software; you can redistribute it and/or modify it under
+   the terms of the GNU General Public License as published by the Free
+   Software Foundation; either version 3, or (at your option) any later
+   version.
+
+   GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+   WARRANTY; without even the implied warranty of MERCHANTABILITY or
+   FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+   for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with GCC; see the file COPYING3.  If not see
+   <http://www.gnu.org/licenses/>.  */
+
+#define IN_TARGET_CODE 1
+
+#include "config.h"
+#include "system.h"
+#include "coretypes.h"
+#include "backend.h"
+#include "rtl.h"
+#include "df.h"
+#include "memmodel.h"
+#include "emit-rtl.h"
+#include "tree-pass.h"
+#include "insn-attr.h"
+#include "insn-constants.h"
+#include "insn-config.h"
+#include "recog.h"
+#include "cfgrtl.h"
+#include "predict.h"
+
+namespace {
+
+/* A non-writeback GPR load pair that is safe for this pass to split.  */
+struct ldp_info
+{
+  rtx dests[2];
+  rtx mem;
+  rtx base;
+  machine_mode mode;
+  HOST_WIDE_INT offset;
+  HOST_WIDE_INT access_size;
+};
+
+/* Return true if REG is a hard general-purpose register in MODE.  */
+
+static bool
+gpr_operand_p (rtx reg, machine_mode mode)
+{
+  return (REG_P (reg)
+	  && HARD_REGISTER_P (reg)
+	  && GP_REGNUM_P (REGNO (reg))
+	  && GET_MODE (reg) == mode);
+}
+
+/* Extract the MEM from one half of an AArch64 load pair.  */
+
+static rtx
+extract_ldp_mem (rtx set, rtx dest, int unspec_code)
+{
+  if (GET_CODE (set) != SET || !rtx_equal_p (SET_DEST (set), dest))
+    return NULL_RTX;
+
+  rtx src = SET_SRC (set);
+  if (GET_CODE (src) != UNSPEC
+      || XINT (src, 1) != unspec_code
+      || XVECLEN (src, 0) != 1)
+    return NULL_RTX;
+
+  rtx mem = XVECEXP (src, 0, 0);
+  return MEM_P (mem) ? mem : NULL_RTX;
+}
+
+/* If INSN is an ordinary, non-writeback SI or DI GPR LDP, describe it in
+   INFO and return true.  Deliberately reject FP/SIMD, TImode, volatile,
+   frame-related, and EH-sensitive forms.  */
+
+static bool
+extract_ldp (rtx_insn *insn, ldp_info *info)
+{
+  if (!NONDEBUG_INSN_P (insn)
+      || INSN_CODE (insn) < 0
+      || get_attr_ldpstp (insn) != LDPSTP_LDP
+      || RTX_FRAME_RELATED_P (insn)
+      || find_reg_note (insn, REG_EH_REGION, NULL_RTX))
+    return false;
+
+  rtx pattern = PATTERN (insn);
+  if (GET_CODE (pattern) != PARALLEL || XVECLEN (pattern, 0) != 2)
+    return false;
+
+  rtx sets[2] = { XVECEXP (pattern, 0, 0), XVECEXP (pattern, 0, 1) };
+  if (GET_CODE (sets[0]) != SET || GET_CODE (sets[1]) != SET)
+    return false;
+
+  info->dests[0] = SET_DEST (sets[0]);
+  info->dests[1] = SET_DEST (sets[1]);
+  info->mode = GET_MODE (info->dests[0]);
+  if ((info->mode != SImode && info->mode != DImode)
+      || !gpr_operand_p (info->dests[0], info->mode)
+      || !gpr_operand_p (info->dests[1], info->mode)
+      || reg_overlap_mentioned_p (info->dests[0], info->dests[1]))
+    return false;
+
+  rtx mem0 = extract_ldp_mem (sets[0], info->dests[0], UNSPEC_LDP_FST);
+  rtx mem1 = extract_ldp_mem (sets[1], info->dests[1], UNSPEC_LDP_SND);
+  if (!mem0
+      || !mem1
+      || !rtx_equal_p (mem0, mem1)
+      || MEM_VOLATILE_P (mem0)
+      || side_effects_p (XEXP (mem0, 0))
+      || !MEM_SIZE_KNOWN_P (mem0)
+      || !MEM_SIZE (mem0).is_constant ())
+    return false;
+
+  info->access_size = GET_MODE_SIZE (info->mode).to_constant ();
+  if (!known_eq (MEM_SIZE (mem0), 2 * info->access_size))
+    return false;
+
+  poly_int64 offset;
+  info->base = strip_offset (XEXP (mem0, 0), &offset);
+  if (!REG_P (info->base) || !offset.is_constant (&info->offset))
+    return false;
+
+  info->mem = mem0;
+  return true;
+}
+
+/* Return true if INSN is a nonvolatile GPR store of one LDP half, using the
+   same unmodified base as INFO and writing exactly the low or high half.  */
+
+static bool
+forwarding_store_p (rtx_insn *insn, const ldp_info &info)
+{
+  if (!NONDEBUG_INSN_P (insn) || RTX_FRAME_RELATED_P (insn))
+    return false;
+
+  rtx set = single_set (insn);
+  if (!set || !MEM_P (SET_DEST (set)))
+    return false;
+
+  rtx mem = SET_DEST (set);
+  rtx value = SET_SRC (set);
+  if (GET_MODE (mem) != info.mode
+      || !gpr_operand_p (value, info.mode)
+      || MEM_VOLATILE_P (mem)
+      || side_effects_p (XEXP (mem, 0))
+      || !MEM_SIZE_KNOWN_P (mem)
+      || !known_eq (MEM_SIZE (mem), info.access_size))
+    return false;
+
+  poly_int64 poly_offset;
+  rtx base = strip_offset (XEXP (mem, 0), &poly_offset);
+  HOST_WIDE_INT offset;
+  if (!REG_P (base)
+      || !rtx_equal_p (base, info.base)
+      || !poly_offset.is_constant (&offset))
+    return false;
+
+  return (offset == info.offset
+	  || offset == info.offset + info.access_size);
+}
+
+/* Search backwards in the containing basic block for a nearby store that
+   makes splitting PAIR profitable.  Stop once the base value changes.  */
+
+static rtx_insn *
+find_forwarding_store (rtx_insn *pair, const ldp_info &info)
+{
+  basic_block bb = BLOCK_FOR_INSN (pair);
+  unsigned int distance = 0;
+  unsigned int max_distance = (unsigned int) aarch64_split_ldp_stlf;
+
+  for (rtx_insn *insn = PREV_INSN (pair);
+       insn && BLOCK_FOR_INSN (insn) == bb;
+       insn = PREV_INSN (insn))
+    {
+      if (!NONDEBUG_INSN_P (insn))
+	continue;
+
+      if (++distance > max_distance)
+	break;
+
+      if (CALL_P (insn) || modified_in_p (info.base, insn))
+	break;
+
+      if (forwarding_store_p (insn, info))
+	return insn;
+    }
+
+  return NULL;
+}
+
+/* Replace PAIR with two individually-recognized scalar loads.  Load the
+   non-base destination first when the low destination overlaps the base.  */
+
+static bool
+split_ldp (rtx_insn *pair, const ldp_info &info)
+{
+  rtx mems[2] = {
+    adjust_address_nv (info.mem, info.mode, 0),
+    adjust_address_nv (info.mem, info.mode, info.access_size)
+  };
+
+  int order[2] = { 0, 1 };
+  if (reg_overlap_mentioned_p (info.base, info.dests[0]))
+    std::swap (order[0], order[1]);
+
+  start_sequence ();
+  emit_insn (gen_rtx_SET (info.dests[order[0]], mems[order[0]]));
+  emit_insn (gen_rtx_SET (info.dests[order[1]], mems[order[1]]));
+  rtx_insn *seq = get_insns ();
+  end_sequence ();
+
+  unshare_all_rtl_in_chain (seq);
+  for (rtx_insn *insn = seq; insn; insn = NEXT_INSN (insn))
+    if (!NONDEBUG_INSN_P (insn) || recog_memoized (insn) < 0)
+      {
+	if (dump_file)
+	  fprintf (dump_file,
+		   "Rejecting LDP %d: scalar replacement is not recognized\n",
+		   INSN_UID (pair));
+	return false;
+      }
+
+  if (dump_file)
+    {
+      fprintf (dump_file, "Splitting LDP %d into:\n", INSN_UID (pair));
+      for (rtx_insn *insn = seq; insn; insn = NEXT_INSN (insn))
+	print_rtl_single (dump_file, insn);
+    }
+
+  emit_insn_before_setloc (seq, pair, INSN_LOCATION (pair));
+  delete_insn (pair);
+  return true;
+}
+
+const pass_data pass_data_split_ldp_stlf =
+{
+  RTL_PASS, /* type.  */
+  "split_ldp_stlf", /* name.  */
+  OPTGROUP_NONE, /* optinfo_flags.  */
+  TV_MACH_DEP, /* tv_id.  */
+  0, /* properties_required.  */
+  0, /* properties_provided.  */
+  0, /* properties_destroyed.  */
+  0, /* todo_flags_start.  */
+  TODO_df_verify | TODO_df_finish, /* todo_flags_finish.  */
+};
+
+class pass_split_ldp_stlf : public rtl_opt_pass
+{
+public:
+  pass_split_ldp_stlf (gcc::context *ctxt)
+    : rtl_opt_pass (pass_data_split_ldp_stlf, ctxt)
+  {}
+
+  bool gate (function *fn) final override
+  {
+    return (optimize >= 1
+	    && !optimize_debug
+	    && reload_completed
+	    && aarch64_split_ldp_stlf
+	    && !optimize_function_for_size_p (fn)
+	    && !fn->can_throw_non_call_exceptions);
+  }
+
+  unsigned int execute (function *fn) final override
+  {
+    unsigned int split_count = 0;
+    basic_block bb;
+
+    FOR_EACH_BB_FN (bb, fn)
+      {
+	rtx_insn *insn;
+	rtx_insn *next;
+	FOR_BB_INSNS_SAFE (bb, insn, next)
+	  {
+	    ldp_info info;
+	    if (!extract_ldp (insn, &info))
+	      continue;
+
+	    rtx_insn *store = find_forwarding_store (insn, info);
+	    if (!store)
+	      continue;
+
+	    if (dump_file)
+	      {
+		fprintf (dump_file, "Found forwarding store for LDP %d:\n",
+			 INSN_UID (insn));
+		print_rtl_single (dump_file, store);
+	      }
+
+	    split_count += split_ldp (insn, info);
+	  }
+      }
+
+    if (dump_file)
+      fprintf (dump_file, "Split %u LDP instructions\n", split_count);
+    return 0;
+  }
+};
+
+} // namespace
+
+rtl_opt_pass *
+make_pass_split_ldp_stlf (gcc::context *ctxt)
+{
+  return new pass_split_ldp_stlf (ctxt);
+}
diff --git a/gcc/config/aarch64/aarch64-passes.def b/gcc/config/aarch64/aarch64-passes.def
index 2df7459af92..594348a0532 100644
--- a/gcc/config/aarch64/aarch64-passes.def
+++ b/gcc/config/aarch64/aarch64-passes.def
@@ -26,4 +26,5 @@ INSERT_PASS_BEFORE (pass_late_thread_prologue_and_epilogue, 1, pass_late_track_s
 INSERT_PASS_BEFORE (pass_shorten_branches, 1, pass_insert_bti);
 INSERT_PASS_BEFORE (pass_early_remat, 1, pass_ldp_fusion);
 INSERT_PASS_BEFORE (pass_peephole2, 1, pass_ldp_fusion);
+INSERT_PASS_BEFORE (pass_sched2, 1, pass_split_ldp_stlf);
 INSERT_PASS_BEFORE (pass_free_cfg, 1, pass_narrow_gp_writes);
diff --git a/gcc/config/aarch64/aarch64-protos.h b/gcc/config/aarch64/aarch64-protos.h
index bcc833cfaa1..ec8be386c29 100644
--- a/gcc/config/aarch64/aarch64-protos.h
+++ b/gcc/config/aarch64/aarch64-protos.h
@@ -1268,6 +1268,7 @@ rtl_opt_pass *make_pass_late_track_speculation (gcc::context *);
 rtl_opt_pass *make_pass_insert_bti (gcc::context *ctxt);
 rtl_opt_pass *make_pass_switch_pstate_sm (gcc::context *ctxt);
 rtl_opt_pass *make_pass_ldp_fusion (gcc::context *);
+rtl_opt_pass *make_pass_split_ldp_stlf (gcc::context *);
 rtl_opt_pass *make_pass_narrow_gp_writes (gcc::context *);
 
 poly_uint64 aarch64_regmode_natural_size (machine_mode);
diff --git a/gcc/config/aarch64/aarch64.opt b/gcc/config/aarch64/aarch64.opt
index deccc6c8887..21e9500996d 100644
--- a/gcc/config/aarch64/aarch64.opt
+++ b/gcc/config/aarch64/aarch64.opt
@@ -461,6 +461,11 @@ individual writeback accesses where possible.  A value of two means we
 also try to opportunistically form writeback opportunities by folding in
 trailing destructive updates of the base register used by a pair.
 
+-param=aarch64-split-ldp-stlf=
+Target Joined UInteger Var(aarch64_split_ldp_stlf) Init(0) IntegerRange(0, 32) Param
+Maximum number of preceding instructions to search when splitting an LDP
+to improve store-to-load forwarding.  A value of zero disables the pass.
+
 -param=aarch64-tag-memory-loop-threshold=
 Target Joined UInteger Var(aarch64_tag_memory_loop_threshold) Init(10) IntegerRange(0, 65536) Param
 Param to control the threshold in number of granules beyond which an
diff --git a/gcc/config/aarch64/t-aarch64 b/gcc/config/aarch64/t-aarch64
index 3942a1049b9..936540aeb4e 100644
--- a/gcc/config/aarch64/t-aarch64
+++ b/gcc/config/aarch64/t-aarch64
@@ -238,6 +238,12 @@ aarch64-ldp-fusion.o: $(srcdir)/config/aarch64/aarch64-ldp-fusion.cc \
 	$(COMPILER) -c $(ALL_COMPILERFLAGS) $(ALL_CPPFLAGS) $(INCLUDES) \
 		$(srcdir)/config/aarch64/aarch64-ldp-fusion.cc
 
+aarch64-ldp-split.o: $(srcdir)/config/aarch64/aarch64-ldp-split.cc \
+    $(CONFIG_H) $(SYSTEM_H) $(CORETYPES_H) $(BACKEND_H) $(RTL_H) $(DF_H) \
+    $(INSN_ATTR_H) insn-config.h $(RECOG_H) tree-pass.h
+	$(COMPILER) -c $(ALL_COMPILERFLAGS) $(ALL_CPPFLAGS) $(INCLUDES) \
+		$(srcdir)/config/aarch64/aarch64-ldp-split.cc
+
 aarch64-sched-dispatch.o: $(srcdir)/config/aarch64/aarch64-sched-dispatch.cc \
     $(CONFIG_H) $(SYSTEM_H) $(CORETYPES_H) $(BACKEND_H) $(RTL_H) \
     $(INSN_ATTR_H) $(REGSET_H) sched-int.h $(DUMPFILE_H) \
diff --git a/gcc/doc/params.texi b/gcc/doc/params.texi
index 26b34a59aa9..1ae93ae6c75 100644
--- a/gcc/doc/params.texi
+++ b/gcc/doc/params.texi
@@ -2061,6 +2061,14 @@ accesses where possible.  A value of two means we also try to opportunistically
 form writeback opportunities by folding in trailing destructive updates of the
 base register used by a pair.
 
+@paindex aarch64-split-ldp-stlf
+@item aarch64-split-ldp-stlf
+Maximum number of preceding instructions that the AArch64 backend searches
+for a scalar store that writes one half of a later @code{ldp}.  When such a
+store is found, the backend can split the @code{ldp} into two scalar loads to
+improve store-to-load forwarding.  A value of zero, which is the default,
+disables the transformation.
+
 @paindex aarch64-loop-vect-issue-rate-niters
 @item aarch64-loop-vect-issue-rate-niters
 The tuning for some AArch64 CPUs tries to take both latencies and issue
diff --git a/gcc/testsuite/gcc.target/aarch64/split-ldp-stlf-1.c b/gcc/testsuite/gcc.target/aarch64/split-ldp-stlf-1.c
new file mode 100644
index 00000000000..ca4ca4821b1
--- /dev/null
+++ b/gcc/testsuite/gcc.target/aarch64/split-ldp-stlf-1.c
@@ -0,0 +1,43 @@
+/* { dg-options "-O2 --param=aarch64-split-ldp-stlf=16" } */
+
+__attribute__ ((noinline, noclone))
+long
+split_low (long *p, long value)
+{
+  p[1] = value;
+  __asm__ __volatile__ ("" ::: "memory");
+  return p[1] + p[2];
+}
+
+__attribute__ ((noinline, noclone))
+long
+split_high (long *p, long value)
+{
+  p[2] = value;
+  __asm__ __volatile__ ("" ::: "memory");
+  return p[1] + p[2];
+}
+
+__attribute__ ((noinline, noclone))
+int
+split_low_si (int *p, int value)
+{
+  p[1] = value;
+  __asm__ __volatile__ ("" ::: "memory");
+  return p[1] + p[2];
+}
+
+extern void consume (long, long);
+
+__attribute__ ((noinline, noclone))
+void
+split_base_dest_overlap (long *p, long value)
+{
+  p[1] = value;
+  __asm__ __volatile__ ("" ::: "memory");
+  consume (p[1], p[2]);
+}
+
+/* { dg-final { scan-assembler-not {\tldp\t} } } */
+/* { dg-final { scan-assembler-times {\tldr\t} 8 } } */
+/* { dg-final { scan-assembler-times {\tldr\tx1, \[x0, 16\]\n\tldr\tx0, \[x0, 8\]} 1 } } */
diff --git a/gcc/testsuite/gcc.target/aarch64/split-ldp-stlf-2.c b/gcc/testsuite/gcc.target/aarch64/split-ldp-stlf-2.c
new file mode 100644
index 00000000000..2ad7e2d5825
--- /dev/null
+++ b/gcc/testsuite/gcc.target/aarch64/split-ldp-stlf-2.c
@@ -0,0 +1,13 @@
+/* { dg-options "-O2" } */
+
+__attribute__ ((noinline, noclone))
+long
+disabled_by_default (long *p, long value)
+{
+  p[1] = value;
+  __asm__ __volatile__ ("" ::: "memory");
+  return p[1] + p[2];
+}
+
+/* { dg-final { scan-assembler-times {\tldp\t} 1 } } */
+/* { dg-final { scan-assembler-not {\tldr\t} } } */
diff --git a/gcc/testsuite/gcc.target/aarch64/split-ldp-stlf-3.c b/gcc/testsuite/gcc.target/aarch64/split-ldp-stlf-3.c
new file mode 100644
index 00000000000..930ada6836d
--- /dev/null
+++ b/gcc/testsuite/gcc.target/aarch64/split-ldp-stlf-3.c
@@ -0,0 +1,22 @@
+/* { dg-options "-O2 --param=aarch64-split-ldp-stlf=16" } */
+
+__attribute__ ((noinline, noclone))
+long
+no_overlap (long *p, long value)
+{
+  p[4] = value;
+  __asm__ __volatile__ ("" ::: "memory");
+  return p[1] + p[2];
+}
+
+__attribute__ ((noinline, noclone))
+long
+wrong_store_width (long *p, int value)
+{
+  *(int *) ((char *) p + 8) = value;
+  __asm__ __volatile__ ("" ::: "memory");
+  return p[1] + p[2];
+}
+
+/* { dg-final { scan-assembler-times {\tldp\t} 2 } } */
+/* { dg-final { scan-assembler-not {\tldr\t} } } */
diff --git a/gcc/testsuite/gcc.target/aarch64/split-ldp-stlf-4.c b/gcc/testsuite/gcc.target/aarch64/split-ldp-stlf-4.c
new file mode 100644
index 00000000000..3e08bcbea05
--- /dev/null
+++ b/gcc/testsuite/gcc.target/aarch64/split-ldp-stlf-4.c
@@ -0,0 +1,14 @@
+/* { dg-options "-O2 --param=aarch64-split-ldp-stlf=16" } */
+/* { dg-additional-options "-fnon-call-exceptions" } */
+
+__attribute__ ((noinline, noclone))
+long
+preserve_faulting_pair (long *p, long value)
+{
+  p[1] = value;
+  __asm__ __volatile__ ("" ::: "memory");
+  return p[1] + p[2];
+}
+
+/* { dg-final { scan-assembler-times {\tldp\t} 1 } } */
+/* { dg-final { scan-assembler-not {\tldr\t} } } */
diff --git a/gcc/testsuite/gcc.target/aarch64/split-ldp-stlf-5.c b/gcc/testsuite/gcc.target/aarch64/split-ldp-stlf-5.c
new file mode 100644
index 00000000000..a19551c4374
--- /dev/null
+++ b/gcc/testsuite/gcc.target/aarch64/split-ldp-stlf-5.c
@@ -0,0 +1,13 @@
+/* { dg-options "-Os --param=aarch64-split-ldp-stlf=16" } */
+
+__attribute__ ((noinline, noclone))
+long
+optimize_for_size (long *p, long value)
+{
+  p[1] = value;
+  __asm__ __volatile__ ("" ::: "memory");
+  return p[1] + p[2];
+}
+
+/* { dg-final { scan-assembler-times {\tldp\t} 1 } } */
+/* { dg-final { scan-assembler-not {\tldr\t} } } */
diff --git a/gcc/testsuite/gcc.target/aarch64/split-ldp-stlf-6.c b/gcc/testsuite/gcc.target/aarch64/split-ldp-stlf-6.c
new file mode 100644
index 00000000000..bd59fe77b47
--- /dev/null
+++ b/gcc/testsuite/gcc.target/aarch64/split-ldp-stlf-6.c
@@ -0,0 +1,13 @@
+/* { dg-options "-O2 --param=aarch64-split-ldp-stlf=1" } */
+
+__attribute__ ((noinline, noclone))
+long
+outside_search_window (long *p, long value)
+{
+  p[1] = value;
+  __asm__ __volatile__ ("" ::: "memory");
+  return p[1] + p[2];
+}
+
+/* { dg-final { scan-assembler-times {\tldp\t} 1 } } */
+/* { dg-final { scan-assembler-not {\tldr\t} } } */
-- 
2.43.0
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.