aarch64: Detect word-level modification in early-ra [PR118184]

REGMODE_NATURAL_SIZE is set to 64 bits for everything except
VLA SVE modes.  This means that it's possible to modify (say)
the highpart of a TI pseudo or a V2DI pseudo independently
of the lowpart.  Modifying such highparts requires a reload
if the highpart ends up in the upper 64 bits of an FPR,
since RTL semantics do not allow the highpart of a single
hard register to be modified independently of the lowpart.

early-ra missed a check for this case, which meant that it
effectively treated an assignment to (subreg:DI (reg:TI R) 0)
as an assignment to the whole of R.

gcc/
	PR target/118184
	* config/aarch64/aarch64-early-ra.cc (allocno_assignment_is_rmw):
	New function.
	(early_ra::record_insn_defs): Mark the live range information as
	untrustworthy if an assignment would change part of an allocno
	but preserve the rest.

gcc/testsuite/
	* gcc.dg/torture/pr118184.c: New test.
This commit is contained in:
Richard Sandiford 2025-01-02 11:34:52 +00:00
parent cb403df46f
commit 2b687ad95d
2 changed files with 86 additions and 1 deletions

View file

@ -2033,6 +2033,43 @@ early_ra::record_artificial_refs (unsigned int flags)
m_current_point += 1;
}
// Return true if:
//
// - X is a SUBREG, in which case it is a SUBREG of some REG Y
//
// - one 64-bit word of Y can be modified while preserving all other words
//
// - X refers to no more than one 64-bit word of Y
//
// - assigning FPRs to Y would put more than one 64-bit word in each FPR
//
// For example, this is true of:
//
// - (subreg:DI (reg:TI R) 0) and
// - (subreg:DI (reg:TI R) 8)
//
// but is not true of:
//
// - (subreg:V2SI (reg:V2x2SI R) 0) or
// - (subreg:V2SI (reg:V2x2SI R) 8).
static bool
allocno_assignment_is_rmw (rtx x)
{
if (partial_subreg_p (x))
{
auto outer_mode = GET_MODE (x);
auto inner_mode = GET_MODE (SUBREG_REG (x));
if (known_eq (REGMODE_NATURAL_SIZE (inner_mode), 0U + UNITS_PER_WORD)
&& known_lt (GET_MODE_SIZE (outer_mode), UNITS_PER_VREG))
{
auto nregs = targetm.hard_regno_nregs (V0_REGNUM, inner_mode);
if (maybe_ne (nregs * UNITS_PER_WORD, GET_MODE_SIZE (inner_mode)))
return true;
}
}
return false;
}
// Called as part of a backwards walk over a block. Model the definitions
// in INSN, excluding partial call clobbers.
void
@ -2045,9 +2082,21 @@ early_ra::record_insn_defs (rtx_insn *insn)
record_fpr_def (DF_REF_REGNO (ref));
else
{
auto range = get_allocno_subgroup (DF_REF_REG (ref));
rtx reg = DF_REF_REG (ref);
auto range = get_allocno_subgroup (reg);
for (auto &allocno : range.allocnos ())
{
// Make sure that assigning to the DF_REF_REG clobbers the
// whole of this allocno, not just some of it.
if (allocno_assignment_is_rmw (reg))
{
record_live_range_failure ([&](){
fprintf (dump_file, "read-modify-write of allocno %d",
allocno.id);
});
break;
}
// If the destination is unused, record a momentary blip
// in its live range.
if (!bitmap_bit_p (m_live_allocnos, allocno.id))

View file

@ -0,0 +1,36 @@
/* { dg-do run { target { longdouble128 && lp64 } } } */
union u1
{
long double ld;
unsigned long l[2];
};
[[gnu::noipa]]
unsigned long m()
{
return 1000;
}
[[gnu::noinline]]
long double f(void)
{
union u1 u;
u.ld = __builtin_nanf128("");
u.l[0] = m();
return u.ld;
}
int main()
{
union u1 u;
u.ld = f();
union u1 u2;
u2.ld = __builtin_nanf128("");
u2.l[0] = m();
if (u.l[0] != u2.l[0])
__builtin_abort();
if (u.l[1] != u2.l[1])
__builtin_abort();
return 0;
}