[llvm] [AArch64] support `byval` arguments in tail calls (PR #206718)
via llvm-commits
llvm-commits at lists.llvm.org
Tue Jun 30 14:46:59 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-backend-aarch64
Author: Folkert de Vries (folkertdev)
<details>
<summary>Changes</summary>
By copying the x86/arm approach.
byval arguments are classified as one of
- `NoCopy`: the argument is already in the right place on the stack, no work needed
- `CopyOnce`: the standard "move the value to the right place", likely using `memcpy`
- `CopyViaTemp`: First copy the value to a temporary, then to the right place on the stack. This is sometimes needed to prevent "stepping on your own tail" and overriding data that you later need.
The rest of the logic is making this work, and making sure that the copy via the temporary has the desired effect.
---
Patch is 31.03 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/206718.diff
8 Files Affected:
- (modified) llvm/include/llvm/CodeGen/GlobalISel/CallLowering.h (+4-1)
- (modified) llvm/lib/Target/AArch64/AArch64ISelLowering.cpp (+147-9)
- (modified) llvm/lib/Target/AArch64/AArch64ISelLowering.h (+17)
- (modified) llvm/lib/Target/AArch64/GISel/AArch64CallLowering.cpp (+82-12)
- (modified) llvm/test/CodeGen/AArch64/GlobalISel/call-translator-tail-call.ll (+2-8)
- (added) llvm/test/CodeGen/AArch64/sibcall-byval.ll (+75)
- (modified) llvm/test/CodeGen/AArch64/tail-call.ll (+37-1)
- (modified) llvm/test/CodeGen/AArch64/tailcc-tail-call.ll (+157-1)
``````````diff
diff --git a/llvm/include/llvm/CodeGen/GlobalISel/CallLowering.h b/llvm/include/llvm/CodeGen/GlobalISel/CallLowering.h
index 110f40a817770..2c835d3c66644 100644
--- a/llvm/include/llvm/CodeGen/GlobalISel/CallLowering.h
+++ b/llvm/include/llvm/CodeGen/GlobalISel/CallLowering.h
@@ -316,7 +316,10 @@ class LLVM_ABI CallLowering {
/// Do a memory copy of \p MemSize bytes from \p SrcPtr to \p DstPtr. This
/// is necessary for outgoing stack-passed byval arguments.
- void
+ ///
+ /// Targets may override this, e.g. to pass tail call arguments through a
+ /// temporary.
+ virtual void
copyArgumentMemory(const ArgInfo &Arg, Register DstPtr, Register SrcPtr,
const MachinePointerInfo &DstPtrInfo, Align DstAlign,
const MachinePointerInfo &SrcPtrInfo, Align SrcAlign,
diff --git a/llvm/lib/Target/AArch64/AArch64ISelLowering.cpp b/llvm/lib/Target/AArch64/AArch64ISelLowering.cpp
index 67ef911117eff..3786439d96917 100644
--- a/llvm/lib/Target/AArch64/AArch64ISelLowering.cpp
+++ b/llvm/lib/Target/AArch64/AArch64ISelLowering.cpp
@@ -9690,14 +9690,12 @@ bool AArch64TargetLowering::isEligibleForTailCallOptimization(
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 (see
- // X86) but less efficient and uglier in LowerCall.
+ // we want to reuse during a tail call. We route such arguments via a
+ // temporary in the current frame, so a function with byval arguments can
+ // still be tail-called.
for (Function::const_arg_iterator i = CallerF.arg_begin(),
e = CallerF.arg_end();
i != e; ++i) {
- if (i->hasByValAttr())
- return false;
-
// On Windows, "inreg" attributes signify non-aggregate indirect returns.
// In this case, it is necessary to save X0/X1 in the callee and return it
// in X0. Tail call opt may interfere with this, so we disable tail call
@@ -9979,6 +9977,51 @@ getSMToggleCondition(const SMECallAttrs &CallAttrs) {
llvm_unreachable("Unsupported attributes");
}
+// Returns the type of copying which is required to set up a byval argument to
+// a tail-called function. This isn't needed for non-tail calls, because they
+// always need the equivalent of CopyOnce, but tail-calls sometimes need two to
+// avoid clobbering another argument (CopyViaTemp), and sometimes can be
+// optimised to zero copies when forwarding an argument from the caller's
+// caller (NoCopy).
+AArch64TargetLowering::ByValCopyKind
+AArch64TargetLowering::ByValNeedsCopyForTailCall(SelectionDAG &DAG, SDValue Src,
+ SDValue Dst,
+ ISD::ArgFlagsTy Flags) const {
+ MachineFrameInfo &MFI = DAG.getMachineFunction().getFrameInfo();
+
+ // Globals and constant pools are not a part of the stack frame, hence
+ // always safe to copy from.
+ if (isa<GlobalAddressSDNode>(Src) || isa<ExternalSymbolSDNode>(Src))
+ return CopyOnce;
+
+ // Can only analyse frame index nodes, conservatively assume we need a
+ // temporary.
+ auto *SrcFrameIdxNode = dyn_cast<FrameIndexSDNode>(Src);
+ auto *DstFrameIdxNode = dyn_cast<FrameIndexSDNode>(Dst);
+ if (!SrcFrameIdxNode || !DstFrameIdxNode)
+ return CopyViaTemp;
+
+ int SrcFI = SrcFrameIdxNode->getIndex();
+ int DstFI = DstFrameIdxNode->getIndex();
+ assert(MFI.isFixedObjectIndex(DstFI) &&
+ "byval passed in non-fixed stack slot");
+
+ int64_t SrcOffset = MFI.getObjectOffset(SrcFI);
+ int64_t DstOffset = MFI.getObjectOffset(DstFI);
+
+ // If the source is in the local frame, then the copy to the argument memory
+ // is always valid.
+ if (!MFI.isFixedObjectIndex(SrcFI) || SrcOffset < 0)
+ return CopyOnce;
+
+ // If the value is already in the correct location, then no copying is
+ // needed. If not, then we need to copy via a temporary.
+ if (SrcOffset == DstOffset)
+ return NoCopy;
+ else
+ return CopyViaTemp;
+}
+
/// Check whether a stack argument requires lowering in a tail call.
static bool shouldLowerTailCallStackArg(const MachineFunction &MF,
const CCValAssign &VA, SDValue Arg,
@@ -10018,6 +10061,20 @@ static bool shouldLowerTailCallStackArg(const MachineFunction &MF,
return true;
}
+/// Make a copy of an aggregate at address specified by "Src" to address
+/// "Dst" with size and alignment information specified by the specific
+/// parameter attribute. The copy will be passed as a byval function parameter.
+static SDValue CreateCopyOfByValArgument(SDValue Src, SDValue Dst,
+ SDValue Chain, ISD::ArgFlagsTy Flags,
+ SelectionDAG &DAG, const SDLoc &dl) {
+ SDValue SizeNode = DAG.getIntPtrConstant(Flags.getByValSize(), dl);
+ Align Alignment = Flags.getNonZeroByValAlign();
+ return DAG.getMemcpy(Chain, dl, Dst, Src, SizeNode, Alignment, Alignment,
+ /*isVolatile*/ false, /*AlwaysInline=*/true,
+ /*CI=*/nullptr, std::nullopt, MachinePointerInfo(),
+ MachinePointerInfo());
+}
+
/// LowerCall - Lower a call to a callseq_start + CALL + callseq_end chain,
/// and add input and output parameter nodes.
SDValue
@@ -10160,6 +10217,74 @@ AArch64TargetLowering::LowerCall(CallLoweringInfo &CLI,
assert(FPDiff % 16 == 0 && "unaligned stack on tail call");
}
+ // If we are doing a tail-call, any byval arguments will be written to stack
+ // space which was used for incoming arguments. If any the values being used
+ // are incoming byval arguments to this function, then they might be
+ // overwritten by the stores of the outgoing arguments. To avoid this, we
+ // need to make a temporary copy of them in local stack space, then copy back
+ // to the argument area.
+ // FIXME: There's potential to improve the code by using virtual registers for
+ // temporary storage, and letting the register allocator spill if needed.
+ SmallVector<SDValue, 8> ByValTemporaries;
+ SDValue ByValTempChain;
+ if (IsTailCall) {
+ // Use null SDValue to mean "no temporary recorded for this arg index".
+ ByValTemporaries.assign(OutVals.size(), SDValue());
+
+ SmallVector<SDValue, 8> ByValCopyChains;
+ for (const CCValAssign &VA : ArgLocs) {
+ unsigned ArgIdx = VA.getValNo();
+ SDValue Src = OutVals[ArgIdx];
+ ISD::ArgFlagsTy Flags = Outs[ArgIdx].Flags;
+
+ if (!Flags.isByVal())
+ continue;
+
+ auto PtrVT = getPointerTy(DAG.getDataLayout());
+
+ // Destination: where this byval should live in the calleeās frame
+ // after the tail call.
+ int64_t Offset = VA.getLocMemOffset() + FPDiff;
+ uint64_t Size = VA.getLocVT().getFixedSizeInBits() / 8;
+ int FI = MF.getFrameInfo().CreateFixedObject(Size, Offset,
+ /*IsImmutable=*/true);
+ SDValue Dst = DAG.getFrameIndex(FI, PtrVT);
+
+ ByValCopyKind Copy = ByValNeedsCopyForTailCall(DAG, Src, Dst, Flags);
+
+ if (Copy == NoCopy) {
+ // If the argument is already at the correct offset on the stack
+ // (because we are forwarding a byval argument from our caller), we
+ // don't need any copying.
+ continue;
+ } else if (Copy == CopyOnce) {
+ // If the argument is in our local stack frame, no other argument
+ // preparation can clobber it, so we can copy it to the final location
+ // later.
+ ByValTemporaries[ArgIdx] = Src;
+ } else {
+ assert(Copy == CopyViaTemp && "unexpected enum value");
+ // If we might be copying this argument from the outgoing argument
+ // stack area, we need to copy via a temporary in the local stack
+ // frame.
+ MachineFrameInfo &MFI = MF.getFrameInfo();
+ int TempFrameIdx = MFI.CreateStackObject(Flags.getByValSize(),
+ Flags.getNonZeroByValAlign(),
+ /*isSS=*/false);
+ SDValue Temp =
+ DAG.getFrameIndex(TempFrameIdx, getPointerTy(DAG.getDataLayout()));
+
+ SDValue CopyChain =
+ CreateCopyOfByValArgument(Src, Temp, Chain, Flags, DAG, DL);
+ ByValCopyChains.push_back(CopyChain);
+ ByValTemporaries[ArgIdx] = Temp;
+ }
+ }
+ if (!ByValCopyChains.empty())
+ ByValTempChain =
+ DAG.getNode(ISD::TokenFactor, DL, MVT::Other, ByValCopyChains);
+ }
+
auto DescribeCallsite =
[&](OptimizationRemarkAnalysis &R) -> OptimizationRemarkAnalysis & {
R << "call from '" << ore::NV("Caller", MF.getName()) << "' to '";
@@ -10218,6 +10343,10 @@ AArch64TargetLowering::LowerCall(CallLoweringInfo &CLI,
}
}
+ if (ByValTempChain)
+ Chain =
+ DAG.getNode(ISD::TokenFactor, DL, MVT::Other, Chain, ByValTempChain);
+
// Walk the register/memloc assignments, inserting copies/loads.
unsigned ExtraArgLocs = 0;
for (unsigned i = 0, e = Outs.size(); i != e; ++i) {
@@ -10394,10 +10523,16 @@ AArch64TargetLowering::LowerCall(CallLoweringInfo &CLI,
int32_t Offset = LocMemOffset + BEAlign;
if (IsTailCall) {
- // When the frame pointer is perfectly aligned for the tail call and the
- // same stack argument is passed down intact, we can reuse it.
- if (!FPDiff && !shouldLowerTailCallStackArg(MF, VA, Arg, Flags, Offset))
+ if (Flags.isByVal()) {
+ if (!ByValTemporaries[i])
+ // The argument is already in the right place.
+ continue;
+ } else if (!FPDiff &&
+ !shouldLowerTailCallStackArg(MF, VA, Arg, Flags, Offset)) {
+ // When the frame pointer is perfectly aligned for the tail call and
+ // the same stack argument is passed down intact, we can reuse it.
continue;
+ }
Offset = Offset + FPDiff;
int FI = MF.getFrameInfo().CreateFixedObject(OpSize, Offset, true);
@@ -10417,10 +10552,13 @@ AArch64TargetLowering::LowerCall(CallLoweringInfo &CLI,
}
if (Outs[i].Flags.isByVal()) {
+ // For tail calls, copy from the (possibly staged) source chosen by the
+ // pre-pass above; the temporary never overlaps the destination.
+ SDValue ByValSrc = IsTailCall ? ByValTemporaries[i] : Arg;
SDValue SizeNode =
DAG.getConstant(Outs[i].Flags.getByValSize(), DL, MVT::i64);
SDValue Cpy = DAG.getMemcpy(
- Chain, DL, DstAddr, Arg, SizeNode,
+ Chain, DL, DstAddr, ByValSrc, SizeNode,
Outs[i].Flags.getNonZeroByValAlign(),
Outs[i].Flags.getNonZeroByValAlign(),
/*isVol = */ false, /*AlwaysInline = */ false,
diff --git a/llvm/lib/Target/AArch64/AArch64ISelLowering.h b/llvm/lib/Target/AArch64/AArch64ISelLowering.h
index 704eed7877bdc..f5facc64c4020 100644
--- a/llvm/lib/Target/AArch64/AArch64ISelLowering.h
+++ b/llvm/lib/Target/AArch64/AArch64ISelLowering.h
@@ -62,6 +62,19 @@ enum : unsigned { PTR32_SPTR = 270, PTR32_UPTR = 271, PTR64 = 272 };
class AArch64Subtarget;
class AArch64TargetLowering : public TargetLowering {
+ // Copying needed for an outgoing byval argument.
+ enum ByValCopyKind {
+ // Argument is already in the correct location, no copy needed.
+ NoCopy,
+ // Argument value is currently in the local stack frame, needs copying to
+ // outgoing arguemnt area.
+ CopyOnce,
+ // Argument value is currently in the outgoing argument area, but not at
+ // the correct offset, so needs copying via a temporary in local stack
+ // space.
+ CopyViaTemp,
+ };
+
public:
explicit AArch64TargetLowering(const TargetMachine &TM,
const AArch64Subtarget &STI);
@@ -601,6 +614,10 @@ class AArch64TargetLowering : public TargetLowering {
// register-name matcher, shared with getRegisterByName.
Register matchRegisterName(StringRef RegName) const;
+ ByValCopyKind ByValNeedsCopyForTailCall(SelectionDAG &DAG, SDValue Src,
+ SDValue Dst,
+ ISD::ArgFlagsTy Flags) const;
+
private:
/// Keep a pointer to the AArch64Subtarget around so that we can
/// make the right decision when generating code for different targets.
diff --git a/llvm/lib/Target/AArch64/GISel/AArch64CallLowering.cpp b/llvm/lib/Target/AArch64/GISel/AArch64CallLowering.cpp
index af88bc51e1ae7..150d39cf70317 100644
--- a/llvm/lib/Target/AArch64/GISel/AArch64CallLowering.cpp
+++ b/llvm/lib/Target/AArch64/GISel/AArch64CallLowering.cpp
@@ -262,8 +262,6 @@ struct OutgoingArgHandler : public CallLowering::OutgoingValueHandler {
LLT s64 = LLT::integer(64);
if (IsTailCall) {
- assert(!Flags.isByVal() && "byval unhandled with tail calls");
-
Offset += FPDiff;
int FI = MF.getFrameInfo().CreateFixedObject(Size, Offset, true);
auto FIReg = MIRBuilder.buildFrameIndex(p0, FI);
@@ -300,6 +298,85 @@ struct OutgoingArgHandler : public CallLowering::OutgoingValueHandler {
MIRBuilder.buildCopy(PhysReg, ExtReg);
}
+ enum ByValCopyKind { CopyOnce, CopyViaTemp, NoCopy };
+ ByValCopyKind classifyByValForTailCall(Register SrcPtr,
+ Register DstPtr) const {
+ const MachineFunction &MF = MIRBuilder.getMF();
+ const MachineFrameInfo &MFI = MF.getFrameInfo();
+
+ // Find the defining instruction (looking through copies).
+ MachineInstr *SrcDef = getDefIgnoringCopies(SrcPtr, MRI);
+ MachineInstr *DstDef = getDefIgnoringCopies(DstPtr, MRI);
+
+ // Convervatively copy when we can't find a root.
+ if (!SrcDef || !DstDef)
+ return CopyViaTemp;
+
+ // Globals are always safe to copy from.
+ if (SrcDef->getOpcode() == TargetOpcode::G_GLOBAL_VALUE ||
+ SrcDef->getOpcode() == TargetOpcode::G_CONSTANT_POOL)
+ return CopyOnce;
+
+ // Can only analyse frame index nodes, conservatively assume we need a
+ // temporary.
+ if (SrcDef->getOpcode() != TargetOpcode::G_FRAME_INDEX ||
+ DstDef->getOpcode() != TargetOpcode::G_FRAME_INDEX)
+ return CopyViaTemp;
+
+ int SrcFI = SrcDef->getOperand(1).getIndex();
+ int64_t SrcOffset = MFI.getObjectOffset(SrcFI);
+
+ // If the source is in the local frame, then the copy to the argument memory
+ // is always valid.
+ if (!MFI.isFixedObjectIndex(SrcFI) || SrcOffset < 0)
+ return CopyOnce;
+
+ // If the value is already in the correct location, then no copying is
+ // needed. If not, then we need to copy via a temporary.
+ int DstFI = DstDef->getOperand(1).getIndex();
+ int64_t DstOffset = MFI.getObjectOffset(DstFI);
+ if (SrcOffset == DstOffset)
+ return NoCopy;
+
+ return CopyViaTemp;
+ }
+
+ void copyArgumentMemory(const CallLowering::ArgInfo &Arg, Register DstPtr,
+ Register SrcPtr, const MachinePointerInfo &DstPtrInfo,
+ Align DstAlign, const MachinePointerInfo &SrcPtrInfo,
+ Align SrcAlign, uint64_t MemSize,
+ CCValAssign &VA) const override {
+ if (IsTailCall) {
+ ByValCopyKind Copy = classifyByValForTailCall(SrcPtr, DstPtr);
+ if (Copy == NoCopy) {
+ // The value is already in the right place.
+ return;
+ } else if (Copy == CopyViaTemp) {
+ // Copy first to a local, then to the destination.
+ MachineFunction &MF = MIRBuilder.getMF();
+ int TempFI = MF.getFrameInfo().CreateStackObject(
+ MemSize, std::max(SrcAlign, DstAlign), /*isSpillSlot=*/false);
+ LLT p0 = LLT::pointer(0, 64);
+ Register Temp = MIRBuilder.buildFrameIndex(p0, TempFI).getReg(0);
+ MachinePointerInfo TempMPO =
+ MachinePointerInfo::getFixedStack(MF, TempFI);
+
+ CallLowering::OutgoingValueHandler::copyArgumentMemory(
+ Arg, Temp, SrcPtr, TempMPO, DstAlign, SrcPtrInfo, SrcAlign, MemSize,
+ VA);
+ CallLowering::OutgoingValueHandler::copyArgumentMemory(
+ Arg, DstPtr, Temp, DstPtrInfo, DstAlign, TempMPO, DstAlign, MemSize,
+ VA);
+ return;
+ }
+ }
+
+ // Otherwise just use the default implementation.
+ CallLowering::OutgoingValueHandler::copyArgumentMemory(
+ Arg, DstPtr, SrcPtr, DstPtrInfo, DstAlign, SrcPtrInfo, SrcAlign,
+ MemSize, VA);
+ }
+
/// Check whether a stack argument requires lowering in a tail call.
static bool shouldLowerTailCallStackArg(const MachineFunction &MF,
const CCValAssign &VA,
@@ -1005,13 +1082,6 @@ bool AArch64CallLowering::isEligibleForTailCallOptimization(
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 (see
- // X86).
- //
- // FIXME: In AArch64ISelLowering, this isn't worked around. Can/should we try
- // it?
- //
// On Windows, "inreg" attributes signify non-aggregate indirect returns.
// In this case, it is necessary to save/restore X0 in the callee. Tail
// call opt interferes with this. So we disable tail call opt when the
@@ -1023,10 +1093,10 @@ bool AArch64CallLowering::isEligibleForTailCallOptimization(
// because would have to move into the swifterror register before the
// tail call.
if (any_of(CallerF.args(), [](const Argument &A) {
- return A.hasByValAttr() || A.hasInRegAttr() || A.hasSwiftErrorAttr();
+ return A.hasInRegAttr() || A.hasSwiftErrorAttr();
})) {
- LLVM_DEBUG(dbgs() << "... Cannot tail call from callers with byval, "
- "inreg, or swifterror arguments\n");
+ LLVM_DEBUG(dbgs() << "... Cannot tail call from callers with inreg"
+ " or swifterror arguments\n");
return false;
}
diff --git a/llvm/test/CodeGen/AArch64/GlobalISel/call-translator-tail-call.ll b/llvm/test/CodeGen/AArch64/GlobalISel/call-translator-tail-call.ll
index 4328ccb94efe2..b5b44dd7d5916 100644
--- a/llvm/test/CodeGen/AArch64/GlobalISel/call-translator-tail-call.ll
+++ b/llvm/test/CodeGen/AArch64/GlobalISel/call-translator-tail-call.ll
@@ -360,19 +360,13 @@ define void @test_byval(ptr byval(i8) %ptr) {
; DARWIN: bb.1 (%ir-block.0):
; DARWIN-NEXT: [[FRAME_INDEX:%[0-9]+]]:_(p0) = G_FRAME_INDEX %fixed-stack.0
; DARWIN-NEXT: [[COPY:%[0-9]+]]:_(p0) = COPY [[FRAME_INDEX]](p0)
- ; DARWIN-NEXT: ADJCALLSTACKDOWN 0, 0, implicit-def $sp, implicit $sp
- ; DARWIN-NEXT: BL @simple_fn, csr_darwin_aarch64_aapcs, implicit-def $lr, implicit $sp
- ; DARWIN-NEXT: ADJCALLSTACKUP 0, 0, implicit-def $sp, implicit $sp
- ; DARWIN-NEXT: RET_ReallyLR
+ ; DARWIN-NEXT: TCRETURNdi @simple_fn, 0, csr_darwin_aarch64_aapcs, implicit $sp
;
; WINDOWS-LABEL: name: test_byval
; WINDOWS: bb.1 (%ir-block.0):
; WINDOWS-NEXT: [[FRAME_INDEX:%[0-9]+]]:_(p0) = G_FRAME_INDEX %fixed-stack.0
; WINDOWS-NEXT: [[COPY:%[0-9]+]]:_(p0) = COPY [[FRAME_INDEX]](p0)
- ; WINDOWS-NEXT: ADJCALLSTACKDOWN 0, 0, implicit-def $sp, implicit $sp
- ; WINDOWS-NEXT: BL @simple_fn, csr_aarch64_aapcs, implicit-def $lr, implicit $sp
- ; WINDOWS-NEXT: ADJCALLSTACKUP 0, 0, implicit-def $sp, implicit $sp
- ; WINDOWS-NEXT: RET_ReallyLR
+ ; WINDOWS-NEXT: TCRETURNdi @simple_fn, 0, csr_aarch64_aapcs, implicit $sp
tail call void @simple_fn()
ret void
}
diff --git a/llvm/test/CodeGen/AArch64/sibcall-byval.ll b/llvm/test/CodeGen/AArch64/sibcall-byval.ll
new file mode 100644
index 0000000000000..2868b586b90d9
--- /dev/null
+++ b/llvm/test/CodeGen/AArch64/sibcall-byval.ll
@@ -0,0 +1,75 @@
+; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py UTC_ARGS: --version 6
+; RUN: llc -mtriple=aarch64-unknown-linux-gnu -O3 %s -verify-machineinstrs -o - | FileCheck %s --check-prefixes=CHECK-SD
+; RUN: llc -mtriple=aarch64-unknown-linux-gnu -O3 -global-isel -global-isel-abort=1 %s -verify-machineinstrs -o - | FileCheck %s --check-prefixes=CHECK-GI
+
+%struct.p = type { i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32 }
+
+define i32 @f(ptr byval(%struct.p) align 4 %q) nounwind {
+; CHECK-SD-LABEL: f:
+; CHECK-SD: ...
[truncated]
``````````
</details>
https://github.com/llvm/llvm-project/pull/206718
More information about the llvm-commits
mailing list