[PATCH v2 0/1] aarch64: Avoid extra move for two-element vector lane copies [PR123951]
Rohith Kapelli <[email protected]> Sun, 2 Aug 2026 22:32:40 +0530
| Newsgroups | gmane.comp.gcc.patches |
|---|---|
| Message-ID | <[email protected]> |
This is v2. It implements the conditions from your last message: the lane
copy pattern is restricted to the case where the source and destination
lanes are the same, and the lane insert pattern to the case where the
destination is architectural lane 0. The v1 expander change in aarch64.cc
is dropped entirely. The general aarch64_simd_vec_copy_lane<mode> and
aarch64_simd_vec_set<mode> patterns are retained unmodified; the two new
patterns sit before them, so everything else, cross-lane copies included,
still goes through the general forms.
I have not implemented the simplify-rtx canonicalisation you mentioned. It
looks like a sensible follow-up, but it is not needed here.
Operand numbering: your operands[2] for the vec_set form was right.
Keeping vec_set's own numbering (0 destination, 1 scalar, 2 mask, 3 vector)
is what makes the pattern recognisable; renumbering the operands produces
RTL that recog rejects.
Which pattern handles what, since it is not obvious from reading them: at
expand a two-element same-lane copy is a vec_select of internal element 1
merged under mask 2, on both endiannesses and for both architectural lanes,
and only the choice of which input is selected from differs. On big endian
element 1 is the lowpart, so the vec_select folds to a subreg and these are
matched by the lane insert pattern; on little endian it is not, so they stay
a vec_select and are matched by the lane copy pattern. Both give a single
INS either way.
On the ?r and Utv alternatives. Both scalar alternatives earn their
place. With ?r and Utv present, on both endiannesses:
uint64x2_t gpr (unsigned long x, uint64x2_t a)
{ return vsetq_lane_u64 (x, a, 0); } ins v0.d[0], x0
uint64x2_t mem (unsigned long *p, uint64x2_t a)
{ return vsetq_lane_u64 (*p, a, 0); } ld1 {v0.d}[0], [x0]
Dropping them does not fall back on the general vec_set pattern, because
the new pattern still matches first, so the scalar has to be moved into a
vector register first:
fmov d31, x0 ldr d31, [x0]
ins v0.d[0], v31.d[0] ins v0.d[0], v31.d[0]
addsub_{1,2}.c: the double add/sub merge builds the same two-element
vec_merge. With the tie left to the register allocator its e1 case now
inserts into lane 0 rather than lane 1. It is still a single INS and the
same instruction count, so I have updated the scan.
Testing. aarch64_be-elf and aarch64-elf crosses, gcc.target/aarch64
including simd/, against banked baselines: zero PASS->FAIL on either
endianness, 6 FAIL->PASS big endian and 4 little endian, no other
transitions. With the patch reverted but the new tests kept, big endian
gives 4 FAILs.
Execution testing under qemu on both endiannesses: a generated battery over
the cross product of the four two-element modes, both source lanes, both
destination lanes, aliased and distinct inputs, and vector/GPR/memory/zero/
constant operands (64 cases), run at -O2, -O1, -Os, -O3, under
-mcpu=thunderx, -mcpu=neoverse-v2 and -mcpu=cortex-a53, and with each of
-fno-tree-pre, -fno-code-hoisting, -fno-tree-dse, -fno-tree-ccp,
-fno-tree-dominator-opts and -fno-schedule-insns disabled individually --
832 cases per endianness, all matching a scalar reference. A further
16-case test covers the other producers of the vec_set shape. Each
alternative of each new pattern was also built as the only alternative of
its pattern and executed, so every alternative is exercised where the
pattern matches rather than only where the allocator happened to pick it;
all pass. Removing either new pattern, or just its second alternative,
costs moves on exactly one endianness and nothing else regresses, so both
tying alternatives are load-bearing.
Bootstrap and regression test on aarch64-linux-gnu, --enable-languages=c,c++,
stage2 and stage3 comparing equal, run on this exact commit: no PASS->FAIL in
gcc, g++, libstdc++, libgomp, libitm or libatomic. The unpatched baseline half
of that comparison had run out of disk partway, so most of the residual
differences are its LTO and libstdc++ tests recovering rather than code
differences; every test affected by that passes in the new run, so no
regression can be hidden behind one. The rest are tsan output-pattern tests,
which are scheduling-dependent -- the same binary reports the race in 2 of 12
identical runs.
Backports: applies unmodified to releases/gcc-16 and releases/gcc-15.
releases/gcc-14 needs one trivial adjustment, as aarch64_simd_vec_set<mode>
is not an @-pattern there.
Rohith Kapelli (1):
aarch64: Avoid extra move for two-element vector lane copies
[PR123951]
gcc/config/aarch64/aarch64-simd.md | 68 +++++++++++++++++++
gcc/testsuite/gcc.target/aarch64/pr123951_1.c | 41 +++++++++++
gcc/testsuite/gcc.target/aarch64/pr123951_2.c | 35 ++++++++++
.../gcc.target/aarch64/simd/addsub_1.c | 2 +-
.../gcc.target/aarch64/simd/addsub_2.c | 2 +-
5 files changed, 146 insertions(+), 2 deletions(-)
create mode 100644 gcc/testsuite/gcc.target/aarch64/pr123951_1.c
create mode 100644 gcc/testsuite/gcc.target/aarch64/pr123951_2.c
--
2.53.0