[llvm] [AVR] Support stack realignment (PR #187129)

Patryk Wychowaniec via llvm-commits llvm-commits at lists.llvm.org
Sun May 10 15:46:22 PDT 2026


https://github.com/Patryk27 updated https://github.com/llvm/llvm-project/pull/187129

>From 2d61aaa3623b64e2c1c0ac6c4f000aa91b37958f Mon Sep 17 00:00:00 2001
From: Patryk Wychowaniec <pwychowaniec at pm.me>
Date: Tue, 17 Mar 2026 21:48:26 +0100
Subject: [PATCH] [AVR] Support stack realignment

---
 llvm/lib/Target/AVR/AVR.h                    |  11 +
 llvm/lib/Target/AVR/AVRExpandPseudoInsts.cpp |   4 +-
 llvm/lib/Target/AVR/AVRFrameLowering.cpp     | 108 ++++-
 llvm/lib/Target/AVR/AVRFrameLowering.h       |   9 +
 llvm/lib/Target/AVR/AVRRegisterInfo.cpp      | 214 +++++---
 llvm/lib/Target/AVR/AVRRegisterInfo.h        |   4 +
 llvm/test/CodeGen/AVR/call.ll                |   2 +-
 llvm/test/CodeGen/AVR/stack-realignment.ll   | 486 +++++++++++++++++++
 8 files changed, 741 insertions(+), 97 deletions(-)
 create mode 100644 llvm/test/CodeGen/AVR/stack-realignment.ll

diff --git a/llvm/lib/Target/AVR/AVR.h b/llvm/lib/Target/AVR/AVR.h
index 68ec9271f75ea..2ae717186442c 100644
--- a/llvm/lib/Target/AVR/AVR.h
+++ b/llvm/lib/Target/AVR/AVR.h
@@ -18,6 +18,7 @@
 #include "llvm/Pass.h"
 #include "llvm/PassRegistry.h"
 #include "llvm/Target/TargetMachine.h"
+#include "AVRTargetMachine.h"
 
 namespace llvm {
 
@@ -51,6 +52,16 @@ enum AddressSpace {
   NumAddrSpaces,
 };
 
+/// Register used to hold frame pointer.
+static constexpr MCPhysReg FPReg = AVR::R29R28;
+static constexpr MCPhysReg FPRegLo = AVR::R28;
+static constexpr MCPhysReg FPRegHi = AVR::R29;
+
+/// Register used to hold stack pointer.
+static constexpr MCPhysReg SPReg = AVR::R17R16; // TODO should probably be R19R18 on Tiny
+static constexpr MCPhysReg SPRegLo = AVR::R16;
+static constexpr MCPhysReg SPRegHi = AVR::R17;
+
 /// Checks if a given type is a pointer to program memory.
 template <typename T> bool isProgramMemoryAddress(T *V) {
   auto *PT = cast<PointerType>(V->getType());
diff --git a/llvm/lib/Target/AVR/AVRExpandPseudoInsts.cpp b/llvm/lib/Target/AVR/AVRExpandPseudoInsts.cpp
index 393b556c9b210..2358b86d9fb47 100644
--- a/llvm/lib/Target/AVR/AVRExpandPseudoInsts.cpp
+++ b/llvm/lib/Target/AVR/AVRExpandPseudoInsts.cpp
@@ -1349,7 +1349,7 @@ bool AVRExpandPseudo::expand<AVR::STDSPQRr>(Block &MBB, BlockIt MBBI) {
   (void)STI;
 
   MI.setDesc(TII->get(AVR::STDPtrQRr));
-  MI.getOperand(0).setReg(AVR::R29R28);
+  MI.getOperand(0).setReg(AVR::FPReg);
 
   return true;
 }
@@ -1368,7 +1368,7 @@ bool AVRExpandPseudo::expand<AVR::STDWSPQRr>(Block &MBB, BlockIt MBBI) {
   (void)STI;
 
   MI.setDesc(TII->get(AVR::STDWPtrQRr));
-  MI.getOperand(0).setReg(AVR::R29R28);
+  MI.getOperand(0).setReg(AVR::FPReg);
 
   return true;
 }
diff --git a/llvm/lib/Target/AVR/AVRFrameLowering.cpp b/llvm/lib/Target/AVR/AVRFrameLowering.cpp
index b919be3d4466d..11a78088a6c01 100644
--- a/llvm/lib/Target/AVR/AVRFrameLowering.cpp
+++ b/llvm/lib/Target/AVR/AVRFrameLowering.cpp
@@ -15,7 +15,6 @@
 #include "AVR.h"
 #include "AVRInstrInfo.h"
 #include "AVRMachineFunctionInfo.h"
-#include "AVRTargetMachine.h"
 #include "MCTargetDesc/AVRMCTargetDesc.h"
 
 #include "llvm/CodeGen/MachineFrameInfo.h"
@@ -94,7 +93,13 @@ void AVRFrameLowering::emitPrologue(MachineFunction &MF,
   }
 
   const MachineFrameInfo &MFI = MF.getFrameInfo();
-  unsigned FrameSize = MFI.getStackSize() - AFI->getCalleeSavedFrameSize();
+  unsigned FrameSize;
+
+  if (STI.getRegisterInfo()->hasStackRealignment(MF)) {
+    FrameSize = MFI.getStackSize() - getOffsetOfLocalArea();
+  } else {
+    FrameSize = MFI.getStackSize() - AFI->getCalleeSavedFrameSize();
+  }
 
   // Skip the callee-saved push instructions.
   while (
@@ -103,14 +108,15 @@ void AVRFrameLowering::emitPrologue(MachineFunction &MF,
     ++MBBI;
   }
 
-  // Update Y with the new base value.
-  BuildMI(MBB, MBBI, DL, TII.get(AVR::SPREAD), AVR::R29R28)
+  // Update FramePtr with the new base value.
+  BuildMI(MBB, MBBI, DL, TII.get(AVR::SPREAD), AVR::FPReg)
       .addReg(AVR::SP)
       .setMIFlag(MachineInstr::FrameSetup);
 
   // Mark the FramePtr as live-in in every block except the entry.
   for (MachineBasicBlock &MBBJ : llvm::drop_begin(MF)) {
-    MBBJ.addLiveIn(AVR::R29R28);
+    MBBJ.addLiveIn(AVR::FPReg);
+    MBBJ.addLiveIn(AVR::SPReg); // TODO should be conditional
   }
 
   if (!FrameSize) {
@@ -121,17 +127,34 @@ void AVRFrameLowering::emitPrologue(MachineFunction &MF,
   unsigned Opcode = (isUInt<6>(FrameSize) && STI.hasADDSUBIW()) ? AVR::SBIWRdK
                                                                 : AVR::SUBIWRdK;
 
-  MachineInstr *MI = BuildMI(MBB, MBBI, DL, TII.get(Opcode), AVR::R29R28)
-                         .addReg(AVR::R29R28, RegState::Kill)
+  MachineInstr *MI = BuildMI(MBB, MBBI, DL, TII.get(Opcode), AVR::FPReg)
+                         .addReg(AVR::FPReg, RegState::Kill)
                          .addImm(FrameSize)
                          .setMIFlag(MachineInstr::FrameSetup);
+
   // The SREG implicit def is dead.
   MI->getOperand(3).setIsDead();
 
-  // Write back R29R28 to SP and temporarily disable interrupts.
+  // Write back FP to SP and temporarily disable interrupts.
   BuildMI(MBB, MBBI, DL, TII.get(AVR::SPWRITE), AVR::SP)
-      .addReg(AVR::R29R28)
+      .addReg(AVR::FPReg)
       .setMIFlag(MachineInstr::FrameSetup);
+
+  if (STI.getRegisterInfo()->hasStackRealignment(MF)) {
+    uint64_t Align = (int)MFI.getMaxAlign().value();
+
+    TII.copyPhysReg(MBB, MBBI, DL, AVR::SPReg, AVR::FPReg, false, false, false);
+
+    BuildMI(MBB, MBBI, DL, TII.get(AVR::SUBIWRdK), AVR::SPReg)
+        .addReg(AVR::SPReg)
+        .addImm(-Align)
+        .setMIFlag(MachineInstr::FrameSetup);
+
+    BuildMI(MBB, MBBI, DL, TII.get(AVR::ANDIWRdK), AVR::SPReg)
+        .addReg(AVR::SPReg)
+        .addImm(-Align)
+        .setMIFlag(MachineInstr::FrameSetup);
+  }
 }
 
 static void restoreStatusRegister(MachineFunction &MF, MachineBasicBlock &MBB) {
@@ -174,10 +197,17 @@ void AVRFrameLowering::emitEpilogue(MachineFunction &MF,
 
   DebugLoc DL = MBBI->getDebugLoc();
   const MachineFrameInfo &MFI = MF.getFrameInfo();
-  unsigned FrameSize = MFI.getStackSize() - AFI->getCalleeSavedFrameSize();
   const AVRSubtarget &STI = MF.getSubtarget<AVRSubtarget>();
   const AVRInstrInfo &TII = *STI.getInstrInfo();
 
+  unsigned FrameSize;
+
+  if (STI.getRegisterInfo()->hasStackRealignment(MF)) {
+    FrameSize = MFI.getStackSize() - getOffsetOfLocalArea();
+  } else {
+    FrameSize = MFI.getStackSize() - AFI->getCalleeSavedFrameSize();
+  }
+
   // Early exit if there is no need to restore the frame pointer.
   if (!FrameSize && !MF.getFrameInfo().hasVarSizedObjects()) {
     restoreStatusRegister(MF, MBB);
@@ -208,20 +238,56 @@ void AVRFrameLowering::emitEpilogue(MachineFunction &MF,
     }
 
     // Restore the frame pointer by doing FP += <size>.
-    MachineInstr *MI = BuildMI(MBB, MBBI, DL, TII.get(Opcode), AVR::R29R28)
-                           .addReg(AVR::R29R28, RegState::Kill)
+    MachineInstr *MI = BuildMI(MBB, MBBI, DL, TII.get(Opcode), AVR::FPReg)
+                           .addReg(AVR::FPReg, RegState::Kill)
                            .addImm(FrameSize);
+
     // The SREG implicit def is dead.
     MI->getOperand(3).setIsDead();
   }
 
-  // Write back R29R28 to SP and temporarily disable interrupts.
+  // Write back FP to SP and temporarily disable interrupts.
   BuildMI(MBB, MBBI, DL, TII.get(AVR::SPWRITE), AVR::SP)
-      .addReg(AVR::R29R28, RegState::Kill);
+      .addReg(AVR::FPReg, RegState::Kill);
 
   restoreStatusRegister(MF, MBB);
 }
 
+StackOffset AVRFrameLowering::getFrameIndexReference(const MachineFunction &MF,
+                                                     int FI,
+                                                     Register &FrameReg) const {
+  const MachineFrameInfo &MFI = MF.getFrameInfo();
+  const TargetRegisterInfo *RI = MF.getSubtarget().getRegisterInfo();
+
+  assert(MFI.getStackID(FI) == TargetStackID::Default && "Unsupported stack");
+
+  int Offset;
+
+  if (RI->hasStackRealignment(MF) && !MFI.isFixedObjectIndex(FI)) {
+    FrameReg = AVR::SPReg;
+
+    Offset = MFI.getObjectOffset(FI) + MFI.getOffsetAdjustment() +
+             MFI.getStackSize() - getOffsetOfLocalArea();
+  } else {
+    FrameReg = AVR::FPReg;
+
+    Offset = MFI.getObjectOffset(FI) + MFI.getOffsetAdjustment() +
+             MFI.getStackSize() - getOffsetOfLocalArea() + 1;
+
+    if (RI->hasStackRealignment(MF)) {
+      const AVRMachineFunctionInfo *AFI = MF.getInfo<AVRMachineFunctionInfo>();
+
+      // TODO feels suspicious, like we're offsetting something that should in
+      //      fact be accounted for elsewhere
+      Offset += 2 + AFI->getCalleeSavedFrameSize();
+    }
+  }
+
+  assert(Offset >= 0);
+
+  return StackOffset::getFixed(Offset);
+}
+
 // Return true if the specified function should have a dedicated frame
 // pointer register. This is true if the function meets any of the following
 // conditions:
@@ -232,10 +298,11 @@ void AVRFrameLowering::emitEpilogue(MachineFunction &MF,
 // Notice that strictly this is not a frame pointer because it contains SP after
 // frame allocation instead of having the original SP in function entry.
 bool AVRFrameLowering::hasFPImpl(const MachineFunction &MF) const {
+  const TargetRegisterInfo *RegInfo = MF.getSubtarget().getRegisterInfo();
   const AVRMachineFunctionInfo *FuncInfo = MF.getInfo<AVRMachineFunctionInfo>();
 
   return (FuncInfo->getHasSpills() || FuncInfo->getHasAllocas() ||
-          FuncInfo->getHasStackArgs() ||
+          FuncInfo->getHasStackArgs() || RegInfo->hasStackRealignment(MF) ||
           MF.getFrameInfo().hasVarSizedObjects());
 }
 
@@ -419,12 +486,17 @@ void AVRFrameLowering::determineCalleeSaves(MachineFunction &MF,
                                             RegScavenger *RS) const {
   TargetFrameLowering::determineCalleeSaves(MF, SavedRegs, RS);
 
-  // If we have a frame pointer, the Y register needs to be saved as well.
   if (hasFP(MF)) {
-    SavedRegs.set(AVR::R29);
-    SavedRegs.set(AVR::R28);
+    SavedRegs.set(AVR::FPRegHi);
+    SavedRegs.set(AVR::FPRegLo);
+
+    if (MF.getSubtarget().getRegisterInfo()->hasStackRealignment(MF)) {
+      SavedRegs.set(AVR::SPRegHi);
+      SavedRegs.set(AVR::SPRegLo);
+    }
   }
 }
+
 /// The frame analyzer pass.
 ///
 /// Scans the function for allocas and used arguments
diff --git a/llvm/lib/Target/AVR/AVRFrameLowering.h b/llvm/lib/Target/AVR/AVRFrameLowering.h
index 7baa5e9d62f60..1859cc1bbfdf2 100644
--- a/llvm/lib/Target/AVR/AVRFrameLowering.h
+++ b/llvm/lib/Target/AVR/AVRFrameLowering.h
@@ -21,6 +21,12 @@ class AVRFrameLowering : public TargetFrameLowering {
 public:
   void emitPrologue(MachineFunction &MF, MachineBasicBlock &MBB) const override;
   void emitEpilogue(MachineFunction &MF, MachineBasicBlock &MBB) const override;
+
+  bool targetHandlesStackFrameRounding() const override { return true; }
+
+  StackOffset getFrameIndexReference(const MachineFunction &MF, int FI,
+                                     Register &FrameReg) const override;
+
   bool spillCalleeSavedRegisters(MachineBasicBlock &MBB,
                                  MachineBasicBlock::iterator MI,
                                  ArrayRef<CalleeSavedInfo> CSI,
@@ -30,10 +36,13 @@ class AVRFrameLowering : public TargetFrameLowering {
                               MachineBasicBlock::iterator MI,
                               MutableArrayRef<CalleeSavedInfo> CSI,
                               const TargetRegisterInfo *TRI) const override;
+
   bool hasReservedCallFrame(const MachineFunction &MF) const override;
   bool canSimplifyCallFramePseudos(const MachineFunction &MF) const override;
+
   void determineCalleeSaves(MachineFunction &MF, BitVector &SavedRegs,
                             RegScavenger *RS = nullptr) const override;
+
   MachineBasicBlock::iterator
   eliminateCallFramePseudoInstr(MachineFunction &MF, MachineBasicBlock &MBB,
                                 MachineBasicBlock::iterator MI) const override;
diff --git a/llvm/lib/Target/AVR/AVRRegisterInfo.cpp b/llvm/lib/Target/AVR/AVRRegisterInfo.cpp
index 18bea848baeab..e6e5e6bd8d9cd 100644
--- a/llvm/lib/Target/AVR/AVRRegisterInfo.cpp
+++ b/llvm/lib/Target/AVR/AVRRegisterInfo.cpp
@@ -16,6 +16,7 @@
 #include "llvm/CodeGen/MachineFrameInfo.h"
 #include "llvm/CodeGen/MachineFunction.h"
 #include "llvm/CodeGen/MachineInstrBuilder.h"
+#include "llvm/CodeGen/RegisterScavenging.h"
 #include "llvm/CodeGen/TargetFrameLowering.h"
 
 #include "AVR.h"
@@ -51,6 +52,8 @@ AVRRegisterInfo::getCallPreservedMask(const MachineFunction &MF,
 }
 
 BitVector AVRRegisterInfo::getReservedRegs(const MachineFunction &MF) const {
+  const AVRSubtarget &Subtarget = MF.getSubtarget<AVRSubtarget>();
+
   BitVector Reserved(getNumRegs());
 
   // Reserve the intermediate result registers r1 and r2
@@ -66,7 +69,7 @@ BitVector AVRRegisterInfo::getReservedRegs(const MachineFunction &MF) const {
   Reserved.set(AVR::SP);
 
   // Reserve R2~R17 only on avrtiny.
-  if (MF.getSubtarget<AVRSubtarget>().hasTinyEncoding()) {
+  if (Subtarget.hasTinyEncoding()) {
     // Reserve 8-bit registers R2~R15, Rtmp(R16) and Zero(R17).
     for (unsigned Reg = AVR::R2; Reg <= AVR::R17; Reg++)
       Reserved.set(Reg);
@@ -75,7 +78,7 @@ BitVector AVRRegisterInfo::getReservedRegs(const MachineFunction &MF) const {
       Reserved.set(Reg);
   }
 
-  // We tenatively reserve the frame pointer register r29:r28 because the
+  // We tentatively reserve the frame pointer register r29:r28 because the
   // function may require one, but we cannot tell until register allocation
   // is complete, which can be too late.
   //
@@ -84,9 +87,15 @@ BitVector AVRRegisterInfo::getReservedRegs(const MachineFunction &MF) const {
   // TODO: Write a pass to enumerate functions which reserved the Y register
   //       but didn't end up needing a frame pointer. In these, we can
   //       convert one or two of the spills inside to use the Y register.
-  Reserved.set(AVR::R28);
-  Reserved.set(AVR::R29);
-  Reserved.set(AVR::R29R28);
+  Reserved.set(AVR::FPReg);
+  Reserved.set(AVR::FPRegLo);
+  Reserved.set(AVR::FPRegHi);
+
+  if (Subtarget.getFrameLowering()->hasFP(MF) && hasStackRealignment(MF)) {
+    Reserved.set(AVR::SPReg);
+    Reserved.set(AVR::SPRegLo);
+    Reserved.set(AVR::SPRegHi);
+  }
 
   return Reserved;
 }
@@ -142,84 +151,81 @@ bool AVRRegisterInfo::eliminateFrameIndex(MachineBasicBlock::iterator II,
                                           int SPAdj, unsigned FIOperandNum,
                                           RegScavenger *RS) const {
   assert(SPAdj == 0 && "Unexpected SPAdj value");
+  assert(RS && "RegScavenger required");
 
   MachineInstr &MI = *II;
-  DebugLoc dl = MI.getDebugLoc();
+  DebugLoc DL = MI.getDebugLoc();
   MachineBasicBlock &MBB = *MI.getParent();
   const MachineFunction &MF = *MBB.getParent();
   const AVRTargetMachine &TM = (const AVRTargetMachine &)MF.getTarget();
   const TargetInstrInfo &TII = *TM.getSubtargetImpl()->getInstrInfo();
-  const MachineFrameInfo &MFI = MF.getFrameInfo();
   const TargetFrameLowering *TFI = TM.getSubtargetImpl()->getFrameLowering();
   const AVRSubtarget &STI = MF.getSubtarget<AVRSubtarget>();
+
   int FrameIndex = MI.getOperand(FIOperandNum).getIndex();
-  int Offset = MFI.getObjectOffset(FrameIndex);
+  int FrameOffset = MI.getOperand(FIOperandNum + 1).getImm();
+  Register FrameReg;
 
-  // Add one to the offset because SP points to an empty slot.
-  Offset += MFI.getStackSize() - TFI->getOffsetOfLocalArea() + 1;
-  // Fold incoming offset.
-  Offset += MI.getOperand(FIOperandNum + 1).getImm();
+  int Offset =
+      TFI->getFrameIndexReference(MF, FrameIndex, FrameReg).getFixed() +
+      FrameOffset;
 
   // This is actually "load effective address" of the stack slot
   // instruction. We have only two-address instructions, thus we need to
   // expand it into move + add.
   if (MI.getOpcode() == AVR::FRMIDX) {
     Register DstReg = MI.getOperand(0).getReg();
-    assert(DstReg != AVR::R29R28 && "Dest reg cannot be the frame pointer");
-
-    // Copy the frame pointer.
-    if (STI.hasMOVW()) {
-      BuildMI(MBB, MI, dl, TII.get(AVR::MOVWRdRr), DstReg).addReg(AVR::R29R28);
-    } else {
-      Register DstLoReg, DstHiReg;
-      splitReg(DstReg, DstLoReg, DstHiReg);
-      BuildMI(MBB, MI, dl, TII.get(AVR::MOVRdRr), DstLoReg).addReg(AVR::R28);
-      BuildMI(MBB, MI, dl, TII.get(AVR::MOVRdRr), DstHiReg).addReg(AVR::R29);
-    }
 
-    assert(Offset > 0 && "Invalid offset");
-
-    // We need to materialize the offset via an add instruction.
-    unsigned Opcode;
-
-    II++; // Skip over the FRMIDX instruction.
-
-    // Generally, to load a frame address two add instructions are emitted that
-    // could get folded into a single one:
-    //  movw    r31:r30, r29:r28
-    //  adiw    r31:r30, 29
-    //  adiw    r31:r30, 16
-    // to:
-    //  movw    r31:r30, r29:r28
-    //  adiw    r31:r30, 45
-    if (II != MBB.end())
-      foldFrameOffset(II, Offset, DstReg);
-
-    // Select the best opcode based on DstReg and the offset size.
-    switch (DstReg) {
-    case AVR::R25R24:
-    case AVR::R27R26:
-    case AVR::R31R30: {
-      if (isUInt<6>(Offset) && STI.hasADDSUBIW()) {
-        Opcode = AVR::ADIWRdK;
+    assert(DstReg != FrameReg && "Dest reg cannot be the frame pointer");
+
+    TII.copyPhysReg(MBB, MI, DL, DstReg, FrameReg, false, false, false);
+
+    if (Offset > 0) {
+      // We need to materialize the offset via an add instruction.
+      unsigned Opcode;
+
+      // Skip over the FRMIDX instruction.
+      II++;
+
+      // Generally, to load a frame address two add instructions are emitted
+      // that could get folded into a single one:
+      //  movw    r31:r30, r29:r28
+      //  adiw    r31:r30, 29
+      //  adiw    r31:r30, 16
+      // to:
+      //  movw    r31:r30, r29:r28
+      //  adiw    r31:r30, 45
+      if (II != MBB.end())
+        foldFrameOffset(II, Offset, DstReg);
+
+      // Select the best opcode based on DstReg and the offset size.
+      switch (DstReg) {
+      case AVR::R25R24:
+      case AVR::R27R26:
+      case AVR::R31R30: {
+        if (isUInt<6>(Offset) && STI.hasADDSUBIW()) {
+          Opcode = AVR::ADIWRdK;
+          break;
+        }
+        [[fallthrough]];
+      }
+      default: {
+        // This opcode will get expanded into a pair of subi/sbci.
+        Opcode = AVR::SUBIWRdK;
+        Offset = -Offset;
         break;
       }
-      [[fallthrough]];
-    }
-    default: {
-      // This opcode will get expanded into a pair of subi/sbci.
-      Opcode = AVR::SUBIWRdK;
-      Offset = -Offset;
-      break;
-    }
-    }
+      }
 
-    MachineInstr *New = BuildMI(MBB, II, dl, TII.get(Opcode), DstReg)
-                            .addReg(DstReg, RegState::Kill)
-                            .addImm(Offset);
-    New->getOperand(3).setIsDead();
+      MachineInstr *New = BuildMI(MBB, II, DL, TII.get(Opcode), DstReg)
+                              .addReg(DstReg, RegState::Kill)
+                              .addImm(Offset);
 
-    MI.eraseFromParent(); // remove FRMIDX
+      New->getOperand(3).setIsDead();
+    }
+
+    // Remove FRMIDX.
+    MI.eraseFromParent();
 
     return false;
   }
@@ -250,42 +256,98 @@ bool AVRRegisterInfo::eliminateFrameIndex(MachineBasicBlock::iterator II,
     // a compare and branch, invalidating the contents of SREG set by the
     // compare instruction because of the add/sub pairs. Conservatively save and
     // restore SREG before and after each add/sub pair.
-    BuildMI(MBB, II, dl, TII.get(AVR::INRdA), STI.getTmpRegister())
+    BuildMI(MBB, II, DL, TII.get(AVR::INRdA), STI.getTmpRegister())
         .addImm(STI.getIORegSREG());
 
-    MachineInstr *New = BuildMI(MBB, II, dl, TII.get(AddOpc), AVR::R29R28)
-                            .addReg(AVR::R29R28, RegState::Kill)
+    MachineInstr *New = BuildMI(MBB, II, DL, TII.get(AddOpc), FrameReg)
+                            .addReg(FrameReg, RegState::Kill)
                             .addImm(AddOffset);
+
     New->getOperand(3).setIsDead();
 
     // Restore SREG.
-    BuildMI(MBB, std::next(II), dl, TII.get(AVR::OUTARr))
+    BuildMI(MBB, std::next(II), DL, TII.get(AVR::OUTARr))
         .addImm(STI.getIORegSREG())
         .addReg(STI.getTmpRegister(), RegState::Kill);
 
     // No need to set SREG as dead here otherwise if the next instruction is a
     // cond branch it will be using a dead register.
-    BuildMI(MBB, std::next(II), dl, TII.get(SubOpc), AVR::R29R28)
-        .addReg(AVR::R29R28, RegState::Kill)
+    BuildMI(MBB, std::next(II), DL, TII.get(SubOpc), FrameReg)
+        .addReg(FrameReg, RegState::Kill)
         .addImm(Offset - MaxOffset);
 
     Offset = MaxOffset;
   }
 
-  MI.getOperand(FIOperandNum).ChangeToRegister(AVR::R29R28, false);
   assert(isUInt<6>(Offset) && "Offset is out of range");
-  MI.getOperand(FIOperandNum + 1).ChangeToImmediate(Offset);
+
+  if (FrameReg == AVR::FPReg) {
+    // Materializing a load for the frame pointer is straightforward - just
+    // replace %stack.0 with the Y register.
+
+    MI.getOperand(FIOperandNum).ChangeToRegister(FrameReg, false);
+    MI.getOperand(FIOperandNum + 1).ChangeToImmediate(Offset);
+  } else {
+    // Materializing a load for the stack pointer is more involved, because we
+    // can't emit ldd for arbitrary registers - we have to project the load
+    // through a temporary.
+
+    bool GotLoadIntoZ = MI.getOpcode() == AVR::LDDWRdPtrQ &&
+                        MI.getOperand(0).isReg() &&
+                        MI.getOperand(0).getReg() == AVR::R31R30;
+
+    bool GotStoreIntoZ = MI.getOpcode() == AVR::STDWPtrQRr &&
+                         MI.getOperand(2).isReg() &&
+                         MI.getOperand(2).getReg() == AVR::R31R30;
+
+    if (GotLoadIntoZ || GotStoreIntoZ) {
+      Register TmpReg = RS->FindUnusedReg(&AVR::PTRREGSRegClass);
+
+      assert(TmpReg && "Couldn't find any unused register");
+
+      TII.copyPhysReg(MBB, MI, DL, TmpReg, FrameReg, false);
+
+      if (Offset != 0) {
+        BuildMI(MBB, II, DL, TII.get(AVR::ADIWRdK))
+            .addDef(TmpReg)
+            .addReg(TmpReg)
+            .addImm(Offset);
+      }
+
+      if (GotLoadIntoZ) {
+        BuildMI(MBB, II, DL, TII.get(AVR::LDWRdPtrPi))
+            .addDef(AVR::R31R30)
+            .addDef(TmpReg)
+            .addReg(TmpReg, RegState::Kill);
+      } else {
+        BuildMI(MBB, II, DL, TII.get(AVR::STWPtrPiRr))
+            .addDef(TmpReg)
+            .addReg(TmpReg, RegState::Kill)
+            .addReg(AVR::R31R30)
+            .addImm(0);
+      }
+
+      MI.eraseFromParent();
+    } else {
+      if (RS->isRegUsed(AVR::R31R30)) {
+        llvm_unreachable("oh noo");
+      }
+
+      TII.copyPhysReg(MBB, MI, DL, AVR::R31R30, FrameReg, false);
+
+      MI.getOperand(FIOperandNum)
+          .ChangeToRegister(AVR::R31R30, false, false, true);
+
+      MI.getOperand(FIOperandNum + 1).ChangeToImmediate(Offset);
+    }
+  }
+
   return false;
 }
 
 Register AVRRegisterInfo::getFrameRegister(const MachineFunction &MF) const {
   const TargetFrameLowering *TFI = MF.getSubtarget().getFrameLowering();
-  if (TFI->hasFP(MF)) {
-    // The Y pointer register
-    return AVR::R28;
-  }
-
-  return AVR::SP;
+  return TFI->hasFP(MF) ? AVR::FPRegLo : AVR::SP;
 }
 
 const TargetRegisterClass *
diff --git a/llvm/lib/Target/AVR/AVRRegisterInfo.h b/llvm/lib/Target/AVR/AVRRegisterInfo.h
index e69696b4d9160..74be5f9b0f928 100644
--- a/llvm/lib/Target/AVR/AVRRegisterInfo.h
+++ b/llvm/lib/Target/AVR/AVRRegisterInfo.h
@@ -43,6 +43,10 @@ class AVRRegisterInfo : public AVRGenRegisterInfo {
 
   Register getFrameRegister(const MachineFunction &MF) const override;
 
+  bool requiresRegisterScavenging(const MachineFunction &MF) const override {
+    return true;
+  }
+
   const TargetRegisterClass *
   getPointerRegClass(unsigned Kind = 0) const override;
 
diff --git a/llvm/test/CodeGen/AVR/call.ll b/llvm/test/CodeGen/AVR/call.ll
index 0054653a7428e..bc28cd211e2ee 100644
--- a/llvm/test/CodeGen/AVR/call.ll
+++ b/llvm/test/CodeGen/AVR/call.ll
@@ -152,7 +152,7 @@ define void @testcallprologue() {
 ; CHECK-LABEL: testcallprologue:
 ; CHECK: push r28
 ; CHECK: push r29
-; CHECK: sbiw r28, 27
+; CHECK: sbiw r28, 16
 ; CHECK: ldi [[REG1:r[0-9]+]], 88
 ; CHECK: std Y+9, [[REG1]]
 ; CHECK: ldi [[REG1:r[0-9]+]], 11
diff --git a/llvm/test/CodeGen/AVR/stack-realignment.ll b/llvm/test/CodeGen/AVR/stack-realignment.ll
new file mode 100644
index 0000000000000..52f34d0581363
--- /dev/null
+++ b/llvm/test/CodeGen/AVR/stack-realignment.ll
@@ -0,0 +1,486 @@
+; RUN: llc -mtriple=avr -mcpu=atmega328 -O1 -verify-machineinstrs < %s | FileCheck %s
+
+declare void @use(ptr %x);
+
+; This function exists for comparison, so that it's easy to see what changes
+; when stack realignment gets activated - no realignment happens here yet.
+define i8 @no_align() {
+; CHECK-LABEL: no_align:
+; CHECK-NEXT: ; %bb.0:
+;
+;; prologue
+; CHECK-NEXT: push r28
+; CHECK-NEXT: push r29
+; CHECK-NEXT: in r28, 61
+; CHECK-NEXT: in r29, 62
+; CHECK-NEXT: sbiw r28, 1
+; CHECK-NEXT: in r0, 63
+; CHECK-NEXT: cli
+; CHECK-NEXT: out 62, r29
+; CHECK-NEXT: out 63, r0
+; CHECK-NEXT: out 61, r28
+;
+;; call void @use(ptr %1)
+; CHECK-NEXT: movw r24, r28
+; CHECK-NEXT: adiw r24, 1
+; CHECK-NEXT: call use
+;
+;; %2 = load i8, ptr %1, align 1
+; CHECK-NEXT: ldd r24, Y+1
+;
+;; epilogue
+; CHECK-NEXT: adiw r28, 1
+; CHECK-NEXT: in r0, 63
+; CHECK-NEXT: cli
+; CHECK-NEXT: out 62, r29
+; CHECK-NEXT: out 63, r0
+; CHECK-NEXT: out 61, r28
+; CHECK-NEXT: pop r29
+; CHECK-NEXT: pop r28
+; CHECK-NEXT: ret
+
+  %1 = alloca i8, align 1
+  call void @use(ptr %1)
+  %2 = load i8, ptr %1, align 1
+
+  ret i8 %2
+}
+
+define i8 @small_align() {
+; CHECK-LABEL: small_align:
+; CHECK-NEXT: %bb.0:
+;
+;; prologue
+; CHECK-NEXT: push r16
+; CHECK-NEXT: push r17
+; CHECK-NEXT: push r28
+; CHECK-NEXT: push r29
+; CHECK-NEXT: in r28, 61
+; CHECK-NEXT: in r29, 62
+; CHECK-NEXT: sbiw r28, 8
+; CHECK-NEXT: in r0, 63
+; CHECK-NEXT: cli
+; CHECK-NEXT: out 62, r29
+; CHECK-NEXT: out 63, r0
+; CHECK-NEXT: out 61, r28
+;
+;; prologue (SP allocation)
+; CHECK-NEXT: movw r16, r28
+; CHECK-NEXT: subi r16, 254
+; CHECK-NEXT: sbci r17, 255
+; CHECK-NEXT: andi r16, 254
+;
+;; call void @use (ptr %1)
+; CHECK-NEXT: movw r24, r16
+; CHECK-NEXT: call use
+;; ^ uses r16 (SP) instead of r28 (FP)
+;
+;; %2 = load i8, ptr %1, align 2
+; CHECK-NEXT: movw r30, r16
+; CHECK-NEXT: ldd r24, Z+0
+;; ^ uses r31r30 as a temporary, since AVR doesn't have `ldd r24, r17r16+0`
+;
+;; epilogue
+; CHECK-NEXT: adiw r28, 8
+; CHECK-NEXT: in r0, 63
+; CHECK-NEXT: cli
+; CHECK-NEXT: out 62, r29
+; CHECK-NEXT: out 63, r0
+; CHECK-NEXT: out 61, r28
+; CHECK-NEXT: pop r29
+; CHECK-NEXT: pop r28
+; CHECK-NEXT: pop r17
+; CHECK-NEXT: pop r16
+; CHECK-NEXT: ret
+
+  %1 = alloca i8, align 2
+  call void @use(ptr %1)
+  %2 = load i8, ptr %1, align 2
+
+  ret i8 %2
+}
+
+define i8 @large_align() {
+; CHECK-LABEL: large_align:
+; CHECK-NEXT: %bb.0:
+;
+;; prologue
+; CHECK-NEXT: push r16
+; CHECK-NEXT: push r17
+; CHECK-NEXT: push r28
+; CHECK-NEXT: push r29
+; CHECK-NEXT: in r28, 61
+; CHECK-NEXT: in r29, 62
+; CHECK-NEXT: sbiw r28, 16
+; CHECK-NEXT: in r0, 63
+; CHECK-NEXT: cli
+; CHECK-NEXT: out 62, r29
+; CHECK-NEXT: out 63, r0
+; CHECK-NEXT: out 61, r28
+;
+;; prologue (SP allocation)
+; CHECK-NEXT: movw r16, r28
+; CHECK-NEXT: subi r16, 240
+; CHECK-NEXT: sbci r17, 255
+; CHECK-NEXT: andi r16, 240
+;
+;; call void @use (ptr %1)
+; CHECK-NEXT: movw r24, r16
+; CHECK-NEXT: call use
+;
+;; %2 = load i8, ptr %1, align 16
+; CHECK-NEXT: movw r30, r16
+; CHECK-NEXT: ldd r24, Z+0
+;
+;; epilogue
+; CHECK-NEXT: adiw r28, 16
+; CHECK-NEXT: in r0, 63
+; CHECK-NEXT: cli
+; CHECK-NEXT: out 62, r29
+; CHECK-NEXT: out 63, r0
+; CHECK-NEXT: out 61, r28
+; CHECK-NEXT: pop r29
+; CHECK-NEXT: pop r28
+; CHECK-NEXT: pop r17
+; CHECK-NEXT: pop r16
+; CHECK-NEXT: ret
+
+  %1 = alloca i8, align 16
+  call void @use(ptr %1)
+  %2 = load i8, ptr %1, align 16
+
+  ret i8 %2
+}
+
+define i8 @align_many() {
+; CHECK-LABEL: align_many:
+; CHECK-NEXT: %bb.0:
+;
+;; prologue
+; CHECK-NEXT: push r16
+; CHECK-NEXT: push r17
+; CHECK-NEXT: push r28
+; CHECK-NEXT: push r29
+; CHECK-NEXT: in r28, 61
+; CHECK-NEXT: in r29, 62
+; CHECK-NEXT: sbiw r28, 24
+; CHECK-NEXT: in r0, 63
+; CHECK-NEXT: cli
+; CHECK-NEXT: out 62, r29
+; CHECK-NEXT: out 63, r0
+; CHECK-NEXT: out 61, r28
+;
+;; prologue (SP allocation)
+; CHECK-NEXT: movw r16, r28
+; CHECK-NEXT: subi r16, 248
+; CHECK-NEXT: sbci r17, 255
+; CHECK-NEXT: andi r16, 248
+;
+;; call void @use (ptr %1)
+; CHECK-NEXT: movw r24, r16
+; CHECK-NEXT: adiw r24, 16
+; CHECK-NEXT: call use
+;
+;; call void @use (ptr %2)
+; CHECK-NEXT: movw r24, r16
+; CHECK-NEXT: adiw r24, 8
+; CHECK-NEXT: call use
+;
+;; call void @use (ptr %3)
+; CHECK-NEXT: movw r24, r16
+; CHECK-NEXT: call use
+;
+;; %5 = load i8, ptr %2, align 8
+; CHECK-NEXT: movw r30, r16
+; CHECK-NEXT: ldd r24, Z+8
+;
+;; %4 = load i8, ptr %1, align 8
+; CHECK-NEXT: movw r30, r16
+; CHECK-NEXT: ldd r25, Z+16
+;
+;; %7 = or i8 %4, %5
+; CHECK-NEXT: or r25, r24
+;
+;; %6 = load i8, ptr %3, align 8
+; CHECK-NEXT: movw r30, r16
+; CHECK-NEXT: ldd r24, Z+0
+;
+;; %8 = or i8 %6, %7
+; CHECK-NEXT: or r24, r25
+;
+;; epilogue
+; CHECK-NEXT: adiw r28, 24
+; CHECK-NEXT: in r0, 63
+; CHECK-NEXT: cli
+; CHECK-NEXT: out 62, r29
+; CHECK-NEXT: out 63, r0
+; CHECK-NEXT: out 61, r28
+; CHECK-NEXT: pop r29
+; CHECK-NEXT: pop r28
+; CHECK-NEXT: pop r17
+; CHECK-NEXT: pop r16
+; CHECK-NEXT: ret
+
+  %1 = alloca i8, align 8
+  %2 = alloca i8, align 8
+  %3 = alloca i8, align 8
+
+  call void @use(ptr %1)
+  call void @use(ptr %2)
+  call void @use(ptr %3)
+
+  %4 = load i8, ptr %1, align 8
+  %5 = load i8, ptr %2, align 8
+  %6 = load i8, ptr %3, align 8
+
+  ; Prevent optimizer from removing the loads
+  %7 = or i8 %4, %5
+  %8 = or i8 %6, %7
+
+  ret i8 %8
+}
+
+define i8 @align_many_mixed() {
+; CHECK-LABEL: align_many_mixed:
+; CHECK-NEXT: %bb.0:
+;
+;; prologue
+; CHECK-NEXT: push r16
+; CHECK-NEXT: push r17
+; CHECK-NEXT: push r28
+; CHECK-NEXT: push r29
+; CHECK-NEXT: in r28, 61
+; CHECK-NEXT: in r29, 62
+; CHECK-NEXT: sbiw r28, 16
+; CHECK-NEXT: in r0, 63
+; CHECK-NEXT: cli
+; CHECK-NEXT: out 62, r29
+; CHECK-NEXT: out 63, r0
+; CHECK-NEXT: out 61, r28
+;
+;; prologue (SP allocation)
+; CHECK-NEXT: movw r16, r28
+; CHECK-NEXT: subi r16, 248
+; CHECK-NEXT: sbci r17, 255
+; CHECK-NEXT: andi r16, 248
+;
+;; call void @use (ptr %1)
+; CHECK-NEXT: movw r24, r16
+; CHECK-NEXT: adiw r24, 8
+; CHECK-NEXT: call use
+;
+;; call void @use (ptr %2)
+; CHECK-NEXT: movw r24, r16
+; CHECK-NEXT: adiw r24, 7
+; CHECK-NEXT: call use
+;
+;; call void @use (ptr %3)
+; CHECK-NEXT: movw r24, r16
+; CHECK-NEXT: call use
+;
+;; %5 = load i8, ptr %2, align 1
+; CHECK-NEXT: movw r30, r16
+; CHECK-NEXT: ldd r24, Z+7
+;
+;; %4 = load i8, ptr %1, align 8
+; CHECK-NEXT: movw r30, r16
+; CHECK-NEXT: ldd r25, Z+8
+;
+;; %7 = or i8 %4, %5
+; CHECK-NEXT: or r25, r24
+;
+;; %6 = load i8, ptr %3, align 8
+; CHECK-NEXT: movw r30, r16
+; CHECK-NEXT: ldd r24, Z+0
+;
+;; %8 = or i8 %6, %7
+; CHECK-NEXT: or r24, r25
+;
+;; epilogue
+; CHECK-NEXT: adiw r28, 16
+; CHECK-NEXT: in r0, 63
+; CHECK-NEXT: cli
+; CHECK-NEXT: out 62, r29
+; CHECK-NEXT: out 63, r0
+; CHECK-NEXT: out 61, r28
+; CHECK-NEXT: pop r29
+; CHECK-NEXT: pop r28
+; CHECK-NEXT: pop r17
+; CHECK-NEXT: pop r16
+; CHECK-NEXT: ret
+
+  %1 = alloca i8, align 8
+  %2 = alloca i8, align 1 ; the square peg
+  %3 = alloca i8, align 8
+
+  call void @use(ptr %1)
+  call void @use(ptr %2)
+  call void @use(ptr %3)
+
+  %4 = load i8, ptr %1, align 8
+  %5 = load i8, ptr %2, align 1
+  %6 = load i8, ptr %3, align 8
+
+  ; Prevent optimizer from removing the loads
+  %7 = or i8 %4, %5
+  %8 = or i8 %6, %7
+
+  ret i8 %8
+}
+
+; Make sure we can carry out both SP-relative and FP-relative loads within a
+; single function.
+;
+; Code below corresponds to:
+;
+; ```
+; uint8_t with_fixed_object(SomeLargeType, uint8_t x) {
+;   _Alignas(8) uint8_t y;
+;
+;   use(&x);
+;   use(&y);
+;
+;   return x | y;
+; }
+; ```
+;
+; ... and what we want to make sure is that `use(&x)` utilizes frame pointer and
+; `use(&y)` utilizes stack pointer.
+;
+; We need for the first argument here to be large, because otherwise `x` (`%2`
+; below) would be passed via registers and we need it to be placed on the stack
+; for `use(&x)` to go through what LLVM calls a _fixed object_.
+;
+; i.e. we need for `%2` to be passed on stack, not via registers
+define i8 @with_fixed_object(i256 %1, i8 %2) {
+; CHECK-LABEL: with_fixed_object:
+; CHECK-NEXT: %bb.0:
+;
+;; prologue
+; CHECK-NEXT: push r16
+; CHECK-NEXT: push r17
+; CHECK-NEXT: push r28
+; CHECK-NEXT: push r29
+; CHECK-NEXT: in r28, 61
+; CHECK-NEXT: in r29, 62
+; CHECK-NEXT: sbiw r28, 8
+; CHECK-NEXT: in r0, 63
+; CHECK-NEXT: cli
+; CHECK-NEXT: out 62, r29
+; CHECK-NEXT: out 63, r0
+; CHECK-NEXT: out 61, r28
+;
+;; prologue (SP allocation)
+; CHECK-NEXT: movw r16, r28
+; CHECK-NEXT: subi r16, 248
+; CHECK-NEXT: sbci r17, 255
+; CHECK-NEXT: andi r16, 248
+;
+;; store i8 %2, ptr %4, align 1
+;; call void @use(ptr %4)
+; CHECK-NEXT: movw r24, r28
+; CHECK-NEXT: adiw r24, 47
+; CHECK-NEXT: call use
+;; ^ note that the store gets eliminated - in a way we use it merely as a proxy
+;;   for what we really want, `addrOf(%2)`
+;
+;; call void @use(ptr %5)
+; CHECK-NEXT: movw r24, r16
+; CHECK-NEXT: call use
+;
+;; %7 = load i8, ptr %5, align 8
+; CHECK-NEXT: movw r30, r16
+; CHECK-NEXT: ldd r25, Z+0
+;
+; %6 = load i8, ptr %4, align 1
+; CHECK-NEXT: ldd r24, Y+47
+;
+;; %8 = or i8 %6, %7
+; CHECK-NEXT: or r24, r25
+;
+;; epilogue
+; CHECK-NEXT: adiw r28, 8
+; CHECK-NEXT: in r0, 63
+; CHECK-NEXT: cli
+; CHECK-NEXT: out 62, r29
+; CHECK-NEXT: out 63, r0
+; CHECK-NEXT: out 61, r28
+; CHECK-NEXT: pop r29
+; CHECK-NEXT: pop r28
+; CHECK-NEXT: pop r17
+; CHECK-NEXT: pop r16
+; CHECK-NEXT: ret
+
+  %4 = alloca i8, align 1
+  %5 = alloca i8, align 8
+
+  store i8 %2, ptr %4, align 1
+
+  call void @use(ptr %4)
+  call void @use(ptr %5)
+
+  %6 = load i8, ptr %4, align 1
+  %7 = load i8, ptr %5, align 8
+  %8 = or i8 %6, %7
+
+  ret i8 %8
+}
+
+; Make sure we can handle cases where LLVM tells us to perform a read into
+; r31r30 itself:
+;
+; ```
+; early-clobber $r31r30 = LDDWRdPtrQ %stack.0
+; ```
+;
+; This is awkward, because for aligned stacks we already use r31r30 as a
+; temporary register to which we copy %stack.0 - i.e. we do something like:
+;
+; ```
+; $r31r30 = COPY %r17r16
+; early-clobber $reg = LDDWRdPtrQ killed %r31r30
+; ```
+;
+; Naturally, this doesn't work when $reg == $r31r30 and so that needs a less
+; optimal expansion.
+define i64 @with_occupied_r31r30(i64 %0) {
+; CHECK-LABEL: with_occupied_r31r30:
+; CHECK-NEXT: %bb.0:
+;
+;; prologue
+; CHECK-NEXT: push r16
+; CHECK-NEXT: push r17
+; CHECK-NEXT: push r28
+; CHECK-NEXT: push r29
+; CHECK-NEXT: in r28, 61
+; CHECK-NEXT: in r29, 62
+; CHECK-NEXT: sbiw r28, 16
+; CHECK-NEXT: in r0, 63
+; CHECK-NEXT: cli
+; CHECK-NEXT: out 62, r29
+; CHECK-NEXT: out 63, r0
+; CHECK-NEXT: out 61, r28
+;
+;; prologue (SP allocation)
+; CHECK-NEXT: movw r16, r28
+; CHECK-NEXT: subi r16, 252
+; CHECK-NEXT: sbci r17, 255
+; CHECK-NEXT: andi r16, 252
+;
+;; %3 = load i64, ptr %2, align 4
+; CHECK-NEXT: push r28
+; CHECK-NEXT: push r29
+; CHECK-NEXT: movw r28, r16
+; CHECK-NEXT: ldd r30, Y+0
+; CHECK-NEXT: ldd r31, Y+1
+; CHECK-NEXT: pop r29
+; CHECK-NEXT: pop r28
+; CHECK-NEXT: or r18, r30
+; CHECK-NEXT: or r19, r31
+
+  %2 = alloca [1 x i64], align 4
+  %3 = load i64, ptr %2, align 4
+  %4 = or i64 %3, %0
+
+  ret i64 %4
+}



More information about the llvm-commits mailing list