Index: lib/Target/RISCV/RISCVAsmPrinter.cpp =================================================================== --- lib/Target/RISCV/RISCVAsmPrinter.cpp +++ lib/Target/RISCV/RISCVAsmPrinter.cpp @@ -69,8 +69,29 @@ void RISCVAsmPrinter::expandFunctionCall(const MachineInstr *MI) { MCInst TmpInst; MachineOperand Func = MI->getOperand(0); + + // For tail call to an address in a register. + // Emit JALR X0, 0(Reg). + // TODO: We had to custom lower this here since for tail calls we cannot + // allow JALR with any GPR; we need to restrict the register class to only + // caller-saved regs [x5-x7]. Hence we had to create a new regclass GPRTC + // which is a subclass of GPR. However, currently tablegen does not support + // the use of the registers of a subclass of the alloweed regclass for an + // instruction. Once this is supported, we can replace the custom lowering + // here with a PseudoInstExpansion rule in RISCVInstrInfo.td. + if (MI->getOpcode() == RISCV::PseudoTCALLri) { + TmpInst = MCInstBuilder(RISCV::JALR) + .addReg(RISCV::X0) + .addReg(Func.getReg()) + .addOperand(MCOperand::createImm(0)); + EmitToStreamer(*OutStreamer, TmpInst); + return; + } + MCSymbol *Sym = nullptr; - unsigned Ra = RISCV::X1; + unsigned Ra = MI->getOpcode() == RISCV::PseudoTCALLdi ? + RISCV::X6 : + RISCV::X1; assert(Func.isGlobal() || Func.isSymbol()); @@ -114,7 +135,9 @@ if (emitPseudoExpansionLowering(*OutStreamer, MI)) return; - if (MI->getOpcode() == RISCV::PseudoCALL) + if (MI->getOpcode() == RISCV::PseudoCALL || + MI->getOpcode() == RISCV::PseudoTCALLdi || + MI->getOpcode() == RISCV::PseudoTCALLri) return expandFunctionCall(MI); MCInst TmpInst; Index: lib/Target/RISCV/RISCVISelLowering.h =================================================================== --- lib/Target/RISCV/RISCVISelLowering.h +++ lib/Target/RISCV/RISCVISelLowering.h @@ -26,7 +26,8 @@ FIRST_NUMBER = ISD::BUILTIN_OP_END, RET_FLAG, CALL, - SELECT_CC + SELECT_CC, + TAILCALL }; } @@ -89,6 +90,11 @@ SDValue lowerVASTART(SDValue Op, SelectionDAG &DAG) const; SDValue LowerFRAMEADDR(SDValue Op, SelectionDAG &DAG) const; SDValue LowerRETURNADDR(SDValue Op, SelectionDAG &DAG) const; + + bool isEligibleForTailCallOptimization(SDValue Callee, + CallingConv::ID CalleeCC, bool IsVarArg, + bool IsCalleeStructRet, MachineFunction &MF, + const SmallVectorImpl &Outs) const; }; } Index: lib/Target/RISCV/RISCVISelLowering.cpp =================================================================== --- lib/Target/RISCV/RISCVISelLowering.cpp +++ lib/Target/RISCV/RISCVISelLowering.cpp @@ -18,6 +18,7 @@ #include "RISCVRegisterInfo.h" #include "RISCVSubtarget.h" #include "RISCVTargetMachine.h" +#include "llvm/ADT/Statistic.h" #include "llvm/CodeGen/CallingConvLower.h" #include "llvm/CodeGen/MachineFrameInfo.h" #include "llvm/CodeGen/MachineFunction.h" @@ -36,6 +37,8 @@ #define DEBUG_TYPE "riscv-lower" +STATISTIC(NumTailCalls, "Number of tail calls"); + RISCVTargetLowering::RISCVTargetLowering(const TargetMachine &TM, const RISCVSubtarget &STI) : TargetLowering(TM), Subtarget(STI) { @@ -733,6 +736,72 @@ return Val; } +/// IsEligibleForTailCallOptimization - Check whether the call is eligible +/// for tail call optimization. +/// Note: This is modelled after ARM's logic for +// IsEligibleForTailCallOptimization. +bool RISCVTargetLowering::isEligibleForTailCallOptimization( + SDValue Callee, CallingConv::ID CalleeCC, bool IsVarArg, + bool IsCalleeStructRet, MachineFunction &MF, + const SmallVectorImpl &Outs) const { + + const Function &CallerF = MF.getFunction(); + CallingConv::ID CallerCC = CallerF.getCallingConv(); + + // If tail calls are disabled for this function. + if (CallerF.getFnAttribute("disable-tail-calls").getValueAsString() == "true") + return false; + + // Do not do tail call optimization for functions with varargs. + if (IsVarArg) + return false; + + // RISCV has 8 function argument registers: [x10-x11], [x12-x17]. + // If a function requires more argument registers we cannot tail call + // optimize it. + if (Outs.size() >= 8) + return false; + + // Exception-handling functions need a special set of instructions to + // indicate a return to the hardware. Tail-calling another function would + // probably break this. + if (CallerF.hasFnAttribute("interrupt")) + return false; + + // Also avoid tailcall optimization if either caller or callee uses struct + // return semantics. + if (IsCalleeStructRet || CallerF.hasStructRetAttr()) + return false; + + // Externally-defined functions with weak linkage should not be + // tail-called. The behaviour of branch instructions in this situation (as + // used for tail calls) is implementation-defined, so we cannot rely on the + // linker replacing the tail call with a return. + if (GlobalAddressSDNode *G = dyn_cast(Callee)) { + const GlobalValue *GV = G->getGlobal(); + if (GV->hasExternalWeakLinkage()) + return false; + } + + // The callee has to preserve all registers the caller needs to preserve. + const RISCVRegisterInfo *TRI = Subtarget.getRegisterInfo(); + const uint32_t *CallerPreserved = TRI->getCallPreservedMask(MF, CallerCC); + if (CalleeCC != CallerCC) { + const uint32_t *CalleePreserved = TRI->getCallPreservedMask(MF, CalleeCC); + if (!TRI->regmaskSubsetEqual(CallerPreserved, CalleePreserved)) + return false; + } + + // Byval parameters hand the function a pointer directly into the stack area + // we want to reuse during a tail call. Working around this *is* possible + // but less efficient and uglier in LowerCall. + for (auto &Arg : Outs) + if (Arg.Flags.isByVal()) + return false; + + return true; +} + // Transform physical registers into virtual registers. SDValue RISCVTargetLowering::LowerFormalArguments( SDValue Chain, CallingConv::ID CallConv, bool IsVarArg, @@ -867,11 +936,12 @@ SmallVectorImpl &Ins = CLI.Ins; SDValue Chain = CLI.Chain; SDValue Callee = CLI.Callee; - CLI.IsTailCall = false; + bool &IsTailCall = CLI.IsTailCall; CallingConv::ID CallConv = CLI.CallConv; bool IsVarArg = CLI.IsVarArg; EVT PtrVT = getPointerTy(DAG.getDataLayout()); MVT XLenVT = Subtarget.getXLenVT(); + bool IsStructRet = Outs.empty() ? false : Outs[0].Flags.isSRet(); MachineFunction &MF = DAG.getMachineFunction(); @@ -880,9 +950,24 @@ CCState ArgCCInfo(CallConv, IsVarArg, MF, ArgLocs, *DAG.getContext()); analyzeOutputArgs(MF, ArgCCInfo, Outs, /*IsRet=*/false, &CLI); + // Check if it's really possible to do a tail call. + if (IsTailCall) + IsTailCall = isEligibleForTailCallOptimization(Callee, CallConv, IsVarArg, + IsStructRet, MF, Outs); + + if (!IsTailCall && CLI.CS && CLI.CS.isMustTailCall()) + report_fatal_error("failed to perform tail call elimination on a call " + "site marked musttail"); + // Get a count of how many bytes are to be pushed on the stack. unsigned NumBytes = ArgCCInfo.getNextStackOffset(); + if (IsTailCall) { + // For tail calls, memory operands are available in our caller's stack. + NumBytes = 0; + ++NumTailCalls; + } + // Create local copies for byval args SmallVector ByValArgs; for (unsigned i = 0, e = Outs.size(); i != e; ++i) { @@ -901,17 +986,21 @@ Chain = DAG.getMemcpy(Chain, DL, FIPtr, Arg, SizeNode, Align, /*IsVolatile=*/false, /*AlwaysInline=*/false, - /*isTailCall=*/false, MachinePointerInfo(), + IsTailCall, MachinePointerInfo(), MachinePointerInfo()); ByValArgs.push_back(FIPtr); } - Chain = DAG.getCALLSEQ_START(Chain, NumBytes, 0, CLI.DL); + if (!IsTailCall) + Chain = DAG.getCALLSEQ_START(Chain, NumBytes, 0, CLI.DL); + + SDValue StackPtr = DAG.getCopyFromReg(Chain, DL, RISCV::X2, + getPointerTy(DAG.getDataLayout())); // Copy argument values to their designated locations. SmallVector, 8> RegsToPass; SmallVector MemOpChains; - SDValue StackPtr; + for (unsigned i = 0, j = 0, e = ArgLocs.size(); i != e; ++i) { CCValAssign &VA = ArgLocs[i]; SDValue ArgValue = OutVals[i]; @@ -920,6 +1009,8 @@ // Promote the value if needed. // For now, only handle fully promoted and indirect arguments. switch (VA.getLocInfo()) { + default: + llvm_unreachable("Unknown loc info!"); case CCValAssign::Full: break; case CCValAssign::BCvt: @@ -949,8 +1040,6 @@ ArgValue = SpillSlot; break; } - default: - llvm_unreachable("Unknown loc info!"); } // Use local copy if it is a byval arg. @@ -966,13 +1055,23 @@ // Work out the address of the stack slot. if (!StackPtr.getNode()) StackPtr = DAG.getCopyFromReg(Chain, DL, RISCV::X2, PtrVT); - SDValue Address = - DAG.getNode(ISD::ADD, DL, PtrVT, StackPtr, - DAG.getIntPtrConstant(VA.getLocMemOffset(), DL)); - // Emit the store. - MemOpChains.push_back( - DAG.getStore(Chain, DL, ArgValue, Address, MachinePointerInfo())); + if (!IsTailCall) { + SDValue Address = + DAG.getNode(ISD::ADD, DL, PtrVT, StackPtr, + DAG.getIntPtrConstant(VA.getLocMemOffset(), DL)); + + // Emit the store. + MemOpChains.push_back( + DAG.getStore(Chain, DL, ArgValue, Address, MachinePointerInfo())); + } else { + MachineFrameInfo &MFI = DAG.getMachineFunction().getFrameInfo(); + int FI = MFI.CreateFixedObject(ArgValue.getValueSizeInBits() / 8, + VA.getLocMemOffset(), false); + SDValue FIN = DAG.getFrameIndex(FI, getPointerTy(DAG.getDataLayout())); + return DAG.getStore(Chain, DL, ArgValue, FIN, MachinePointerInfo(), + /* Alignment = */ 0, MachineMemOperand::MOVolatile); + } } } @@ -982,11 +1081,24 @@ SDValue Glue; + // Tail call byval lowering might overwrite argument registers so in case of + // tail call optimization the copies to registers are lowered to 'real' stack + // slot. + // Force all the incoming stack arguments to be loaded from the stack + // before any new outgoing arguments are stored to the stack, because the + // outgoing stack slots may alias the incoming argument stack slots, and + // the alias isn't otherwise explicit. This is slightly more conservative + // than necessary, because it means that each store effectively depends + // on every argument instead of just those arguments it would clobber. + if (IsTailCall) + Glue = SDValue(); // Build a sequence of copy-to-reg nodes, chained and glued together. for (auto &Reg : RegsToPass) { Chain = DAG.getCopyToReg(Chain, DL, Reg.first, Reg.second, Glue); Glue = Chain.getValue(1); } + if (IsTailCall) + Glue = SDValue(); // If the callee is a GlobalAddress/ExternalSymbol node, turn it into a // TargetGlobalAddress/TargetExternalSymbol node so that legalize won't @@ -1010,11 +1122,13 @@ for (auto &Reg : RegsToPass) Ops.push_back(DAG.getRegister(Reg.first, Reg.second.getValueType())); - // Add a register mask operand representing the call-preserved registers. - const TargetRegisterInfo *TRI = Subtarget.getRegisterInfo(); - const uint32_t *Mask = TRI->getCallPreservedMask(MF, CallConv); - assert(Mask && "Missing call preserved mask for calling convention"); - Ops.push_back(DAG.getRegisterMask(Mask)); + if (!IsTailCall) { + // Add a register mask operand representing the call-preserved registers. + const TargetRegisterInfo *TRI = Subtarget.getRegisterInfo(); + const uint32_t *Mask = TRI->getCallPreservedMask(MF, CallConv); + assert(Mask && "Missing call preserved mask for calling convention"); + Ops.push_back(DAG.getRegisterMask(Mask)); + } // Glue the call to the argument copies, if any. if (Glue.getNode()) @@ -1022,6 +1136,12 @@ // Emit the call. SDVTList NodeTys = DAG.getVTList(MVT::Other, MVT::Glue); + + if (IsTailCall) { + MF.getFrameInfo().setHasTailCall(); + return DAG.getNode(RISCVISD::TAILCALL, DL, NodeTys, Ops); + } + Chain = DAG.getNode(RISCVISD::CALL, DL, NodeTys, Ops); Glue = Chain.getValue(1); @@ -1145,6 +1265,8 @@ return "RISCVISD::CALL"; case RISCVISD::SELECT_CC: return "RISCVISD::SELECT_CC"; + case RISCVISD::TAILCALL: + return "RISCVISD::TAILCALL"; } return nullptr; } Index: lib/Target/RISCV/RISCVInstrInfo.cpp =================================================================== --- lib/Target/RISCV/RISCVInstrInfo.cpp +++ lib/Target/RISCV/RISCVInstrInfo.cpp @@ -379,7 +379,10 @@ case TargetOpcode::KILL: case TargetOpcode::DBG_VALUE: return 0; + case RISCV::PseudoTCALLri: + return 4; case RISCV::PseudoCALL: + case RISCV::PseudoTCALLdi: return 8; case TargetOpcode::INLINEASM: { const MachineFunction &MF = *MI.getParent()->getParent(); Index: lib/Target/RISCV/RISCVInstrInfo.td =================================================================== --- lib/Target/RISCV/RISCVInstrInfo.td +++ lib/Target/RISCV/RISCVInstrInfo.td @@ -38,6 +38,8 @@ [SDNPHasChain, SDNPOptInGlue, SDNPVariadic]>; def SelectCC : SDNode<"RISCVISD::SELECT_CC", SDT_RISCVSelectCC, [SDNPInGlue]>; +def TailCall : SDNode<"RISCVISD::TAILCALL", SDT_RISCVCall, + [SDNPHasChain, SDNPOptInGlue, SDNPVariadic]>; //===----------------------------------------------------------------------===// // Operand and SDNode transformation definitions. @@ -626,7 +628,7 @@ def : Pat<(brind (add GPR:$rs1, simm12:$imm12)), (PseudoBRIND GPR:$rs1, simm12:$imm12)>; -// PseudoCALL is a pseudo instruction which will eventaully expand to auipc +// PseudoCALL is a pseudo instruction which will eventually expand to auipc // and jalr. let isCall = 1, Defs = [X1] in def PseudoCALL : Pseudo<(outs), (ins call_target:$func), @@ -642,6 +644,18 @@ def PseudoRET : Pseudo<(outs), (ins), [(RetFlag)]>, PseudoInstExpansion<(JALR X0, X1, 0)>; +let isCall = 1, isTerminator = 1, isReturn = 1, isBarrier = 1, Uses = [X2] in { + def PseudoTCALLdi : Pseudo<(outs), (ins call_target:$dst), []>; + def PseudoTCALLri : Pseudo<(outs), (ins GPRTC:$dst), []>; +} + +def : Pat<(TailCall (iPTR tglobaladdr:$dst)), + (PseudoTCALLdi texternalsym:$dst)>; +def : Pat<(TailCall (iPTR texternalsym:$dst)), + (PseudoTCALLdi texternalsym:$dst)>; +def : Pat<(TailCall GPRTC:$dst), + (PseudoTCALLri GPRTC:$dst)>; + /// Loads multiclass LdPat { Index: lib/Target/RISCV/RISCVRegisterInfo.td =================================================================== --- lib/Target/RISCV/RISCVRegisterInfo.td +++ lib/Target/RISCV/RISCVRegisterInfo.td @@ -128,6 +128,16 @@ [RegInfo<32,32,32>, RegInfo<64,64,64>, RegInfo<32,32,32>]>; } +// For tail calls, we can't use callee-saved registers, as they are restored to +// the saved value before the tail call, which would clobber a call address. +def GPRTC : RegisterClass<"RISCV", [XLenVT], 32, (add + (sequence "X%u", 5, 7) + )> { + let RegInfos = RegInfoByHwMode< + [RV32, RV64, DefaultMode], + [RegInfo<32,32,32>, RegInfo<64,64,64>, RegInfo<32,32,32>]>; +} + def SP : RegisterClass<"RISCV", [XLenVT], 32, (add X2)> { let RegInfos = RegInfoByHwMode< [RV32, RV64, DefaultMode], Index: test/CodeGen/RISCV/disable-tail-calls.ll =================================================================== --- /dev/null +++ test/CodeGen/RISCV/disable-tail-calls.ll @@ -0,0 +1,70 @@ +; RUN: llc < %s -mtriple=riscv32-unknown-linux-gnu \ +; RUN: | FileCheck %s --check-prefix=NO-OPTION +; RUN: llc < %s -mtriple=riscv32-unknown-linux-gnu -disable-tail-calls \ +; RUN: | FileCheck %s --check-prefix=DISABLE-TRUE +; RUN: llc < %s -mtriple=riscv32-unknown-linux-gnu -disable-tail-calls=false \ +; RUN: | FileCheck %s --check-prefix=DISABLE-FALSE + +; RUN: llc < %s -mtriple=riscv32-unknown-elf \ +; RUN: | FileCheck %s --check-prefix=NO-OPTION +; RUN: llc < %s -mtriple=riscv32-unknown-elf -disable-tail-calls \ +; RUN: | FileCheck %s --check-prefix=DISABLE-TRUE +; RUN: llc < %s -mtriple=riscv32-unknown-elf -disable-tail-calls=false \ +; RUN: | FileCheck %s --check-prefix=DISABLE-FALSE + +; RUN: llc < %s -mtriple=riscv64-unknown-linux-gnu \ +; RUN: | FileCheck %s --check-prefix=NO-OPTION +; RUN: llc < %s -mtriple=riscv64-unknown-linux-gnu -disable-tail-calls \ +; RUN: | FileCheck %s --check-prefix=DISABLE-TRUE +; RUN: llc < %s -mtriple=riscv64-unknown-linux-gnu -disable-tail-calls=false \ +; RUN: | FileCheck %s --check-prefix=DISABLE-FALSE + +; RUN: llc < %s -mtriple=riscv64-unknown-elf \ +; RUN: | FileCheck %s --check-prefix=NO-OPTION +; RUN: llc < %s -mtriple=riscv64-unknown-elf -disable-tail-calls \ +; RUN: | FileCheck %s --check-prefix=DISABLE-TRUE +; RUN: llc < %s -mtriple=riscv64-unknown-elf -disable-tail-calls=false \ +; RUN: | FileCheck %s --check-prefix=DISABLE-FALSE + +; Check that command line option "-disable-tail-calls" overrides function +; attribute "disable-tail-calls". + +; NO-OPTION-LABEL: {{\_?}}func_attr +; NO-OPTION: auipc ra, %pcrel_hi(callee) +; NO-OPTION: jalr ra, ra, %pcrel_lo(.Ltmp0) + +; DISABLE-FALSE-LABEL: {{\_?}}func_attr +; DISABLE-FALSE: auipc t1, %pcrel_hi(callee) +; DISABLE-FALSE: jalr t1, t1, %pcrel_lo(.Ltmp0) + +; DISABLE-TRUE-LABEL: {{\_?}}func_attr +; DISABLE-TRUE: auipc ra, %pcrel_hi(callee) +; DISABLE-TRUE: jalr ra, ra, %pcrel_lo(.Ltmp0) + +define i32 @func_attr(i32 %a) #0 { +entry: + %call = tail call i32 @callee(i32 %a) + ret i32 %call +} + +; NO-OPTION-LABEL: {{\_?}}func_noattr +; NO-OPTION: auipc t1, %pcrel_hi(callee) +; NO-OPTION: jalr t1, t1, %pcrel_lo(.Ltmp1) + +; DISABLE-FALSE-LABEL: {{\_?}}func_noattr +; DISABLE-FALSE: auipc t1, %pcrel_hi(callee) +; DISABLE-FALSE: jalr t1, t1, %pcrel_lo(.Ltmp1) + +; DISABLE-TRUE-LABEL: {{\_?}}func_noattr +; DISABLE-TRUE: auipc ra, %pcrel_hi(callee) +; DISABLE-TRUE: jalr ra, ra, %pcrel_lo(.Ltmp1) + +define i32 @func_noattr(i32 %a) { +entry: + %call = tail call i32 @callee(i32 %a) + ret i32 %call +} + +declare i32 @callee(i32) + +attributes #0 = { "disable-tail-calls"="true" } Index: test/CodeGen/RISCV/tail-calls.ll =================================================================== --- /dev/null +++ test/CodeGen/RISCV/tail-calls.ll @@ -0,0 +1,90 @@ +; RUN: llc -mtriple riscv32-unknown-linux-gnu -o - < %s | FileCheck %s +; RUN: llc -mtriple riscv32-unknown-elf -o - < %s | FileCheck %s +; RUN: llc -mtriple riscv64-unknown-linux-gnu -o - < %s | FileCheck %s +; RUN: llc -mtriple riscv64-unknown-elf -o - < %s | FileCheck %s + +; Perform tail call optimization. +declare i32 @callee(i32 %i) +define i32 @caller(i32 %i) { +; CHECK-LABEL: caller +; CHECK: auipc t1, %pcrel_hi(callee) +; CHECK: jalr t1, t1, %pcrel_lo(.Ltmp0) +entry: + %r = tail call i32 @callee(i32 %i) + ret i32 %r +} + +; Do not do tail call optimization for functions with varargs. +declare i32 @variadic(i32, ...) +define void @v_caller_ints1(i32 %a, i32 %b) { +; CHECK-LABEL: v_caller_ints1: +; CHECK: auipc ra, %pcrel_hi(variadic) +; CHECK: jalr ra, ra, %pcrel_lo(.Ltmp1) +; CHECK-NOT: auipc t1, %pcrel_hi(callee) +; CHECK-NOT: jalr t1, t1, %pcrel_lo(.Ltmp0) +entry: + %call = tail call i32 (i32, ...) @variadic(i32 %a, i32 %b, i32 %b, i32 %a) + ret void +} + +; RISCV has 8 function argument registers: [x10-x11], [x12-x17]. +; If a function requires more argument registers we cannot tail call optimize it. +declare i32 @callee_args(i32 %a, i32 %b, i32 %c, i32 %dd, i32 %e, i32 %ff, i32 %g, i32 %h, i32 %i) +define i32 @caller_args(i32 %a, i32 %b, i32 %c, i32 %dd, i32 %e, i32 %ff, i32 %g, i32 %h, i32 %i) { +; CHECK-LABEL: caller_args +; CHECK: auipc ra, %pcrel_hi(callee_args) +; CHECK: jalr ra, ra, %pcrel_lo(.Ltmp2) +; CHECK-NOT: auipc t1, %pcrel_hi(callee_args) +; CHECK-NOT: jalr t1, t1, %pcrel_lo(.Ltmp0) +entry: + %r = tail call i32 @callee_args(i32 %a, i32 %b, i32 %c, i32 %dd, i32 %e, i32 %ff, i32 %g, i32 %h, i32 %i) + ret i32 %r +} + +; Externally-defined functions with weak linkage should not be tail-called. +; The behaviour of branch instructions in this situation (as used for tail +; calls) is implementation-defined, so we cannot rely on the linker replacing +; the tail call with a return. +declare extern_weak void @callee_weak() +define void @caller_weak() { +; CHECK-LABEL: caller_weak: +; CHECK: auipc ra, %pcrel_hi(callee_weak) +; CHECK: jalr ra, ra, %pcrel_lo(.Ltmp3) +; CHECK-NOT: auipc t1, %pcrel_hi(callee_weak) +; CHECK-NOT: jalr t1, t1, %pcrel_lo(.Ltmp0) +entry: + tail call void @callee_weak() + ret void +} + +; Exception-handling functions need a special set of instructions to indicate a +; return to the hardware. Tail-calling another function would probably break +; this. +declare void @callee_irq() +define void @caller_irq() #0 { +; CHECK-LABEL: caller_irq: +; CHECK: auipc ra, %pcrel_hi(callee_irq) +; CHECK: jalr ra, ra, %pcrel_lo(.Ltmp4) +; CHECK-NOT: auipc t1, %pcrel_hi(callee_irq) +; CHECK-NOT: jalr t1, t1, %pcrel_lo(.Ltmp0) +entry: + tail call void @callee_irq() + ret void +} +attributes #0 = { "interrupt" } + +; Byval parameters hand the function a pointer directly into the stack area +; we want to reuse during a tail call. Do not tail call optimize functions with +; byval parameters. +declare i32 @callee_byval(i32** byval %a) +define i32 @caller_byval() { +; CHECK-LABEL: caller_byval: +; CHECK: auipc ra, %pcrel_hi(callee_byval) +; CHECK: jalr ra, ra, %pcrel_lo(.Ltmp5) +; CHECK-NOT: auipc t1, %pcrel_hi(callee_byval) +; CHECK-NOT: jalr t1, t1, %pcrel_lo(.Ltmp0) +entry: + %a = alloca i32* + %r = tail call i32 @callee_byval(i32** byval %a) + ret i32 %r +}