https://github.com/llvmbot created https://github.com/llvm/llvm-project/pull/228967
Backport 660dbc9ef14ec76259b82a6d8118d4850d3b7d3d Requested by: @alexrp >From cca9db22489b6b8b7f8796f32b42f9354e198615 Mon Sep 17 00:00:00 2001 From: Ben Shi <[email protected]> Date: Sat, 19 Sep 2026 04:27:09 +0800 Subject: [PATCH] [AVR] Implement selection of 'ISD::RETURNADDR' and 'ISD::FRAMEADDR' (#222909) (cherry picked from commit 660dbc9ef14ec76259b82a6d8118d4850d3b7d3d) --- llvm/lib/Target/AVR/AVRFrameLowering.cpp | 11 +- llvm/lib/Target/AVR/AVRISelLowering.cpp | 89 +++++++++++++++ llvm/lib/Target/AVR/AVRISelLowering.h | 2 + llvm/test/CodeGen/AVR/frameaddress.ll | 74 +++++++++++++ llvm/test/CodeGen/AVR/returnaddress.ll | 131 +++++++++++++++++++++++ 5 files changed, 305 insertions(+), 2 deletions(-) create mode 100644 llvm/test/CodeGen/AVR/frameaddress.ll create mode 100644 llvm/test/CodeGen/AVR/returnaddress.ll diff --git a/llvm/lib/Target/AVR/AVRFrameLowering.cpp b/llvm/lib/Target/AVR/AVRFrameLowering.cpp index b919be3d4466d..14beaaaed6607 100644 --- a/llvm/lib/Target/AVR/AVRFrameLowering.cpp +++ b/llvm/lib/Target/AVR/AVRFrameLowering.cpp @@ -228,15 +228,22 @@ void AVRFrameLowering::emitEpilogue(MachineFunction &MF, // - a register has been spilled // - has allocas // - input arguments are passed using the stack +// - has variable sized objects +// - the frame address is taken (llvm.frameaddress) +// - the return address is taken (llvm.returnaddress) // // 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 MachineFrameInfo &MFI = MF.getFrameInfo(); const AVRMachineFunctionInfo *FuncInfo = MF.getInfo<AVRMachineFunctionInfo>(); + // Note that reading the return address requires a frame index, and that frame + // indexes are always referenced through Y (see + // AVRRegisterInfo::eliminateFrameIndex). return (FuncInfo->getHasSpills() || FuncInfo->getHasAllocas() || - FuncInfo->getHasStackArgs() || - MF.getFrameInfo().hasVarSizedObjects()); + FuncInfo->getHasStackArgs() || MFI.hasVarSizedObjects() || + MFI.isFrameAddressTaken() || MFI.isReturnAddressTaken()); } bool AVRFrameLowering::spillCalleeSavedRegisters( diff --git a/llvm/lib/Target/AVR/AVRISelLowering.cpp b/llvm/lib/Target/AVR/AVRISelLowering.cpp index 8fd1c34af9d7b..79ed56679120b 100644 --- a/llvm/lib/Target/AVR/AVRISelLowering.cpp +++ b/llvm/lib/Target/AVR/AVRISelLowering.cpp @@ -50,6 +50,8 @@ AVRTargetLowering::AVRTargetLowering(const AVRTargetMachine &TM, setOperationAction(ISD::GlobalAddress, MVT::i16, Custom); setOperationAction(ISD::BlockAddress, MVT::i16, Custom); + setOperationAction(ISD::FRAMEADDR, MVT::i16, Custom); + setOperationAction(ISD::RETURNADDR, MVT::i16, Custom); setOperationAction(ISD::STACKSAVE, MVT::Other, Expand); setOperationAction(ISD::STACKRESTORE, MVT::Other, Expand); @@ -961,11 +963,98 @@ SDValue AVRTargetLowering::LowerOperation(SDValue Op, SelectionDAG &DAG) const { return LowerDivRem(Op, DAG); case ISD::INLINEASM: return LowerINLINEASM(Op, DAG); + case ISD::FRAMEADDR: + return LowerFRAMEADDR(Op, DAG); + case ISD::RETURNADDR: + return LowerRETURNADDR(Op, DAG); } return SDValue(); } +SDValue AVRTargetLowering::LowerFRAMEADDR(SDValue Op, SelectionDAG &DAG) const { + // The frame pointer (Y = r29:r28) is set up to contain the stack pointer + // *after* the frame has been allocated, i.e. Y == SP_entry - StackSize, + // which means it points to the lowest address of the frame: it is really a + // frame *base* rather than the canonical frame address. The slot holding the + // caller's Y therefore sits at a function dependent offset (the frame size) + // above it. Walking the frame chain is only possible for the current frame. + // + // This is also what avr-gcc returns for __builtin_frame_address(0). + if (Op.getConstantOperandVal(0) > 0) + // Use the legalizer's default expansion, which is to return 0 (what this + // function is documented to do). + return SDValue(); + + MachineFrameInfo &MFI = DAG.getMachineFunction().getFrameInfo(); + // Mark frame address as taken, so that during frame lowering we're forced to + // emit frame pointer register (R29R28) we rely on here. + MFI.setFrameAddressIsTaken(true); + + // Note that AVRRegisterInfo::getFrameRegister returns R28, which is only + // the low half of the frame pointer: use the full 16-bit register pair. + return DAG.getCopyFromReg(DAG.getEntryNode(), SDLoc(Op), AVR::R29R28, + Op.getValueType()); +} + +SDValue AVRTargetLowering::LowerRETURNADDR(SDValue Op, + SelectionDAG &DAG) const { + // AVR has no link register: the return address is pushed onto the stack by + // the call instruction. The stack pointer always points at the first free + // byte, so the pushed return address ends up right below the local area (the + // incoming stack arguments), which starts at SP_entry + 3. + // + // The return address is pushed most significant byte first, hence it is + // stored big-endian: [SP_entry + 1] is the high byte and [SP_entry + 2] the + // low byte. This is why avr-gcc has to swap the two bytes it reads for + // __builtin_return_address(0). + // + // Walking the frame chain is not possible, because the distance between the + // frame base and the slot holding the caller's return address depends on the + // caller's frame size, which is not known here. This is also what avr-gcc + // does for __builtin_return_address(1), which returns zero. + if (Op.getConstantOperandVal(0) > 0) + // Use the legalizer's default expansion, which is to return 0 (what this + // function is documented to do). + return SDValue(); + + MachineFunction &MF = DAG.getMachineFunction(); + MachineFrameInfo &MFI = MF.getFrameInfo(); + // Mark return address as taken, so that during frame lowering we're forced to + // emit frame pointer register (R29R28) we rely on here. + MFI.setReturnAddressIsTaken(true); + + SDLoc DL(Op); + EVT PtrVT = getPointerTy(DAG.getDataLayout()); + + // A call pushes as many bytes as the program counter is wide: three on + // devices with a 22-bit PC (the ones providing EIJMP/EICALL), two elsewhere. + // Only the two low bytes of the return address are returned, so on the former + // they are found one byte higher up. + int PCWidth = Subtarget.hasEIJMPCALL() ? 3 : 2; + + // A fixed object at offset N is located at SP_entry + N + 3, so the two low + // bytes of the return address are the fixed object at offset PCWidth - 4. + // Note that a frame index can only be referenced through Y, so taking the + // return address forces the function to have a frame pointer (see + // AVRFrameLowering::hasFPImpl). + int FI = MFI.CreateFixedObject(2, PCWidth - 4, true); + SDValue Addr = DAG.getFrameIndex(FI, PtrVT); + + // Load the two bytes separately and put them in the right halves of the + // result, which is cheaper than loading a word and byte swapping it. + SDValue Hi = DAG.getLoad(MVT::i8, DL, DAG.getEntryNode(), Addr, + MachinePointerInfo::getFixedStack(MF, FI)); + SDValue Lo = + DAG.getLoad(MVT::i8, DL, DAG.getEntryNode(), + DAG.getObjectPtrOffset(DL, Addr, TypeSize::getFixed(1)), + MachinePointerInfo::getFixedStack(MF, FI, 1)); + + SDValue Res = DAG.getTargetInsertSubreg(AVR::sub_lo, DL, MVT::i16, + DAG.getUNDEF(MVT::i16), Lo); + return DAG.getTargetInsertSubreg(AVR::sub_hi, DL, MVT::i16, Res, Hi); +} + /// Replace a node with an illegal result type /// with a new node built out of custom code. void AVRTargetLowering::ReplaceNodeResults(SDNode *N, diff --git a/llvm/lib/Target/AVR/AVRISelLowering.h b/llvm/lib/Target/AVR/AVRISelLowering.h index 2ae22b2d8af6c..ae99f654aba00 100644 --- a/llvm/lib/Target/AVR/AVRISelLowering.h +++ b/llvm/lib/Target/AVR/AVRISelLowering.h @@ -108,6 +108,8 @@ class AVRTargetLowering : public TargetLowering { SDValue LowerSELECT_CC(SDValue Op, SelectionDAG &DAG) const; SDValue LowerSETCC(SDValue Op, SelectionDAG &DAG) const; SDValue LowerVASTART(SDValue Op, SelectionDAG &DAG) const; + SDValue LowerFRAMEADDR(SDValue Op, SelectionDAG &DAG) const; + SDValue LowerRETURNADDR(SDValue Op, SelectionDAG &DAG) const; bool CanLowerReturn(CallingConv::ID CallConv, MachineFunction &MF, bool isVarArg, diff --git a/llvm/test/CodeGen/AVR/frameaddress.ll b/llvm/test/CodeGen/AVR/frameaddress.ll new file mode 100644 index 0000000000000..8fcf519b5bd62 --- /dev/null +++ b/llvm/test/CodeGen/AVR/frameaddress.ll @@ -0,0 +1,74 @@ +; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py UTC_ARGS: --version 6 +; RUN: llc < %s -mtriple=avr | FileCheck %s + +declare ptr @llvm.frameaddress.p0(i32 immarg) +declare void @use(ptr) + +; The frame pointer is Y (r29:r28), which is set up to contain the stack +; pointer after the frame has been allocated. +define ptr @frameaddress_0() { +; CHECK-LABEL: frameaddress_0: +; CHECK: ; %bb.0: +; CHECK-NEXT: push r28 +; CHECK-NEXT: push r29 +; CHECK-NEXT: in r28, 61 +; CHECK-NEXT: in r29, 62 +; CHECK-NEXT: mov r24, r28 +; CHECK-NEXT: mov r25, r29 +; CHECK-NEXT: pop r29 +; CHECK-NEXT: pop r28 +; CHECK-NEXT: ret + %1 = call ptr @llvm.frameaddress.p0(i32 0) + ret ptr %1 +} + +; Walking the frame chain is not possible, because the slot holding the +; caller's Y is at a function dependent offset above Y. Return zero instead. +define ptr @frameaddress_1() { +; CHECK-LABEL: frameaddress_1: +; CHECK: ; %bb.0: +; CHECK-NEXT: ldi r24, 0 +; CHECK-NEXT: ldi r25, 0 +; CHECK-NEXT: ret + %1 = call ptr @llvm.frameaddress.p0(i32 1) + ret ptr %1 +} + +; Local objects are addressed relative to Y, the same value that is returned +; by llvm.frameaddress(0). +define ptr @frameaddress_alloca() { +; CHECK-LABEL: frameaddress_alloca: +; CHECK: ; %bb.0: +; CHECK-NEXT: push r28 +; CHECK-NEXT: push r29 +; CHECK-NEXT: in r28, 61 +; CHECK-NEXT: in r29, 62 +; CHECK-NEXT: sbiw r28, 4 +; 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: ldi r24, 42 +; CHECK-NEXT: std Y+1, r24 +; CHECK-NEXT: mov r24, r28 +; CHECK-NEXT: mov r25, r29 +; CHECK-NEXT: adiw r24, 1 +; CHECK-NEXT: rcall use +; CHECK-NEXT: mov r24, r28 +; CHECK-NEXT: mov r25, r29 +; CHECK-NEXT: adiw r28, 4 +; 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 + %x = alloca i8, i32 4 + store i8 42, ptr %x + call void @use(ptr %x) + %1 = call ptr @llvm.frameaddress.p0(i32 0) + ret ptr %1 +} diff --git a/llvm/test/CodeGen/AVR/returnaddress.ll b/llvm/test/CodeGen/AVR/returnaddress.ll new file mode 100644 index 0000000000000..634b11fca92c3 --- /dev/null +++ b/llvm/test/CodeGen/AVR/returnaddress.ll @@ -0,0 +1,131 @@ +; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py UTC_ARGS: --version 6 +; RUN: llc < %s -mtriple=avr | FileCheck %s +; RUN: llc < %s -mtriple=avr -mcpu=atmega2560 | FileCheck %s --check-prefix=AVR6 + +declare ptr @llvm.returnaddress.p0(i32 immarg) +declare void @use(ptr) + +; AVR has no link register: the return address is pushed onto the stack by the +; call instruction. The stack pointer points at the first free byte, so the +; pushed return address ends up right below the local area, which starts at +; SP_entry + 3. +; +; The return address is pushed most significant byte first, hence it is stored +; big-endian: [SP_entry + 1] is the high byte and [SP_entry + 2] the low byte. +; This is why the two bytes have to be swapped (avr-gcc does the same). +define ptr @returnaddress_0() { +; CHECK-LABEL: returnaddress_0: +; CHECK: ; %bb.0: +; CHECK-NEXT: push r28 +; CHECK-NEXT: push r29 +; CHECK-NEXT: in r28, 61 +; CHECK-NEXT: in r29, 62 +; CHECK-NEXT: ldd r24, Y+4 +; CHECK-NEXT: ldd r25, Y+3 +; CHECK-NEXT: pop r29 +; CHECK-NEXT: pop r28 +; CHECK-NEXT: ret +; +; AVR6-LABEL: returnaddress_0: +; AVR6: ; %bb.0: +; AVR6-NEXT: push r28 +; AVR6-NEXT: push r29 +; AVR6-NEXT: in r28, 61 +; AVR6-NEXT: in r29, 62 +; AVR6-NEXT: ldd r24, Y+5 +; AVR6-NEXT: ldd r25, Y+4 +; AVR6-NEXT: pop r29 +; AVR6-NEXT: pop r28 +; AVR6-NEXT: ret +; Devices with a 22-bit program counter push one byte more, so the two low +; bytes of the return address are found one byte higher up. + %1 = call ptr @llvm.returnaddress.p0(i32 0) + ret ptr %1 +} + +; Walking the frame chain is not possible, because the distance between the +; frame base and the slot holding the caller's return address depends on the +; caller's frame size, which is not known here. Return zero instead. +define ptr @returnaddress_1() { +; CHECK-LABEL: returnaddress_1: +; CHECK: ; %bb.0: +; CHECK-NEXT: ldi r24, 0 +; CHECK-NEXT: ldi r25, 0 +; CHECK-NEXT: ret +; +; AVR6-LABEL: returnaddress_1: +; AVR6: ; %bb.0: +; AVR6-NEXT: ldi r24, 0 +; AVR6-NEXT: ldi r25, 0 +; AVR6-NEXT: ret + %1 = call ptr @llvm.returnaddress.p0(i32 1) + ret ptr %1 +} + +; The return address is always the two bytes just below the incoming stack +; arguments, so its offset relative to Y grows with the frame size. +define ptr @returnaddress_alloca() { +; CHECK-LABEL: returnaddress_alloca: +; CHECK: ; %bb.0: +; CHECK-NEXT: push r28 +; CHECK-NEXT: push r29 +; CHECK-NEXT: in r28, 61 +; CHECK-NEXT: in r29, 62 +; CHECK-NEXT: sbiw r28, 4 +; 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: ldi r24, 42 +; CHECK-NEXT: std Y+1, r24 +; CHECK-NEXT: mov r24, r28 +; CHECK-NEXT: mov r25, r29 +; CHECK-NEXT: adiw r24, 1 +; CHECK-NEXT: rcall use +; CHECK-NEXT: ldd r24, Y+8 +; CHECK-NEXT: ldd r25, Y+7 +; CHECK-NEXT: adiw r28, 4 +; 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 +; +; AVR6-LABEL: returnaddress_alloca: +; AVR6: ; %bb.0: +; AVR6-NEXT: push r28 +; AVR6-NEXT: push r29 +; AVR6-NEXT: in r28, 61 +; AVR6-NEXT: in r29, 62 +; AVR6-NEXT: sbiw r28, 4 +; AVR6-NEXT: in r0, 63 +; AVR6-NEXT: cli +; AVR6-NEXT: out 62, r29 +; AVR6-NEXT: out 63, r0 +; AVR6-NEXT: out 61, r28 +; AVR6-NEXT: ldi r24, 42 +; AVR6-NEXT: std Y+1, r24 +; AVR6-NEXT: movw r24, r28 +; AVR6-NEXT: adiw r24, 1 +; AVR6-NEXT: call use +; AVR6-NEXT: ldd r24, Y+9 +; AVR6-NEXT: ldd r25, Y+8 +; AVR6-NEXT: adiw r28, 4 +; AVR6-NEXT: in r0, 63 +; AVR6-NEXT: cli +; AVR6-NEXT: out 62, r29 +; AVR6-NEXT: out 63, r0 +; AVR6-NEXT: out 61, r28 +; AVR6-NEXT: pop r29 +; AVR6-NEXT: pop r28 +; AVR6-NEXT: ret + %x = alloca i8, i32 4 + store i8 42, ptr %x + call void @use(ptr %x) + %1 = call ptr @llvm.returnaddress.p0(i32 0) + ret ptr %1 +} _______________________________________________ llvm-branch-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-branch-commits
