[llvm] [AArch64] Reorder callee-saved spills to maximise load/store register groupings (PR #229349)
Kieran B via llvm-commits
llvm-commits at lists.llvm.org
Wed Oct 7 07:50:33 PDT 2026
================
@@ -2863,102 +2863,94 @@ static void orderZPRCalleeSavesForGroups(MachineFunction &MF,
if (ZPRSaves.size() < 2)
return;
- llvm::sort(ZPRSaves, [](const auto &A, const auto &B) {
- return A.getReg() < B.getReg();
- });
+ int ZPRByteOffset = AFI->getZPRCalleeSavedStackSize();
+ if (!AFI->hasSplitSVEObjects())
+ ZPRByteOffset += AFI->getPPRCalleeSavedStackSize();
+
+ const int Scale = RegInfo->getSpillSize(AArch64::ZPRRegClass);
+ const int LowestOffset =
+ (ZPRByteOffset / Scale) - static_cast<int>(ZPRSaves.size());
+
+ int NumToAlign = alignTo(LowestOffset, 4) - LowestOffset;
+ if (NumToAlign > int(ZPRSaves.size()))
+ return;
+
+ sort(ZPRSaves,
+ [](const auto &A, const auto &B) { return A.getReg() < B.getReg(); });
SmallVector<std::array<CalleeSavedInfo, 4>> Quads;
SmallVector<std::array<CalleeSavedInfo, 2>> Pairs;
SmallVector<CalleeSavedInfo> Singles;
+
+ int ZPRsNeededForAlignment = NumToAlign;
for (size_t i = 0; i < ZPRSaves.size();) {
+ int ZPRsRemaining = int(ZPRSaves.size()) - i;
if (i + 3 < ZPRSaves.size() &&
(ZPRSaves[i].getReg() - AArch64::Z0) % 4 == 0 &&
- (ZPRSaves[i].getReg() + 3 == ZPRSaves[i + 3].getReg())) {
+ ZPRSaves[i].getReg() + 3 == ZPRSaves[i + 3].getReg() &&
+ ZPRsRemaining - 4 >= ZPRsNeededForAlignment) {
Quads.push_back(
{ZPRSaves[i], ZPRSaves[i + 1], ZPRSaves[i + 2], ZPRSaves[i + 3]});
i += 4;
- } else if (i + 1 < ZPRSaves.size() &&
- (ZPRSaves[i].getReg() - AArch64::Z0) % 2 == 0 &&
- ZPRSaves[i].getReg() + 1 == ZPRSaves[i + 1].getReg()) {
- Pairs.push_back({ZPRSaves[i], ZPRSaves[i + 1]});
- i += 2;
- } else {
- Singles.push_back(ZPRSaves[i++]);
+ continue;
}
- }
- if (Quads.empty() && Pairs.empty())
- return;
-
- int ZPRByteOffset = AFI->getZPRCalleeSavedStackSize();
- if (!AFI->hasSplitSVEObjects())
- ZPRByteOffset += AFI->getPPRCalleeSavedStackSize();
+ bool MustSplitPairForSingle =
+ ZPRsNeededForAlignment == 1 && Singles.empty() && ZPRsRemaining == 2;
- const int Scale = RegInfo->getSpillSize(AArch64::ZPRRegClass);
- const int LowestOffset =
- (ZPRByteOffset / Scale) - static_cast<int>(ZPRSaves.size());
+ if (i + 1 < ZPRSaves.size() &&
+ (ZPRSaves[i].getReg() - AArch64::Z0) % 2 == 0 &&
+ ZPRSaves[i].getReg() + 1 == ZPRSaves[i + 1].getReg() &&
+ !MustSplitPairForSingle) {
+ Pairs.push_back({ZPRSaves[i], ZPRSaves[i + 1]});
- auto TakeSingle = [&]() -> CalleeSavedInfo {
- if (!Singles.empty())
- return Singles.pop_back_val();
+ if (ZPRsNeededForAlignment >= 2) {
+ ZPRsNeededForAlignment -= 2;
+ } else if (ZPRsNeededForAlignment == 1 && !Singles.empty()) {
+ // In this case, we have already found a single to use for alignment but
+ // we prefer to use this pair instead to align the offset
+ ZPRsNeededForAlignment = 0;
+ }
- if (!Pairs.empty()) {
- auto [a, b] = Pairs.pop_back_val();
- Singles.push_back(a);
- return b;
+ i += 2;
+ continue;
}
- if (!Quads.empty()) {
- auto [a, b, c, d] = Quads.pop_back_val();
- Pairs.push_back({a, b});
- Singles.push_back(c);
- return d;
- }
+ Singles.push_back(ZPRSaves[i++]);
+ if (ZPRsNeededForAlignment > 0)
+ ZPRsNeededForAlignment--;
+ }
- llvm_unreachable("Register groups exhausted before output was complete");
- };
+ if (Quads.empty() && Pairs.empty())
+ return;
int NumUnassigned = ZPRSaves.size();
- auto Emit = [&](const CalleeSavedInfo &CS) {
+ auto Emit = [&](ArrayRef<CalleeSavedInfo> Saves) {
// During pair/quad formation, we check for the register groups in reverse
// order. This is to ensures that stack indexes remain consistent with the
// instruction.
- CSI[ZPRPositions[--NumUnassigned]] = CS;
+ for (const CalleeSavedInfo &CS : Saves)
+ CSI[ZPRPositions[--NumUnassigned]] = CS;
};
- // With an odd offset, the Emit(single) branch will be reached first,
- // aligning to an even offset. Then the Emit(pair) branch will be reached,
- // aligning to an offset divisible by 4. The algorithm minimally decomposes
- // quads/pairs when needed.
- int Offset = LowestOffset;
- while (NumUnassigned != 0) {
- if (Offset % 4 == 0 && !Quads.empty()) {
- for (const auto &ZReg : Quads.pop_back_val())
- Emit(ZReg);
-
- Offset += 4;
- continue;
- }
- if (Offset % 2 == 0) {
- // Two singles can advance an even offset to the quad-aligned
- // offset, preserve the quad in this case
- if (Pairs.empty() && Singles.size() < 2 && !Quads.empty()) {
- const auto &[a, b, c, d] = Quads.pop_back_val();
- Pairs.push_back({a, b});
- Pairs.push_back({c, d});
- }
+ // Try to align the offset to mul 4 (to allow for the most quads/pairs).
+ if (NumToAlign & 0b01)
+ Emit(Singles.pop_back_val());
+ if (NumToAlign & 0b10) {
+ if (!Pairs.empty())
+ Emit(Pairs.pop_back_val());
+ else
+ Emit({Singles.pop_back_val(), Singles.pop_back_val()});
+ }
- if (!Pairs.empty()) {
- for (const auto &ZReg : Pairs.pop_back_val())
- Emit(ZReg);
- Offset += 2;
- continue;
- }
- }
+ for (const auto &Quad : Quads)
----------------
kieroxide wrote:
We could reverse all Quads, Pairs and singles here to keep register spills from high to low (somewhat). But doesn't change anything but style.
https://github.com/llvm/llvm-project/pull/229349
More information about the llvm-commits
mailing list