[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