[llvm] fix preserve_none in X86 backend (PR #192300)
via llvm-commits
llvm-commits at lists.llvm.org
Wed Apr 15 10:54:59 PDT 2026
llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-backend-x86
Author: Jerry Dang (kuroyukiasuna)
<details>
<summary>Changes</summary>
This is a issue that's specific to the X86 backend: preserve_none with -O1, IR looks like:
```
tail call preserve_nonecc void @<!-- -->_Z14func(ptr noundef nonnull byval(%struct.S) align 8 %1)
```
Where we have tailcall on a func with preserve_none cc, and a byval in the parameter.
This slips through the `isEligibleForSiblingCallOpt` check and thinks this is a sibling call, because `byval` for `preserve_none` is passed as a register, not memory. As a result, the lowering logic thinks this is a sibling call and does not reserve stack space for it. But preserve_none with byval would try copy outgoing args in the stack, corrupting the frame. This leads to a runtime crash.
The propose fix is to check for non-standard CC like `preserve_none` and disqualify it for sibling call.
Fixes #<!-- -->188930
---
Full diff: https://github.com/llvm/llvm-project/pull/192300.diff
2 Files Affected:
- (modified) llvm/lib/Target/X86/X86ISelLoweringCall.cpp (+9)
- (modified) llvm/test/CodeGen/X86/preserve_nonecc_call.ll (+18)
``````````diff
diff --git a/llvm/lib/Target/X86/X86ISelLoweringCall.cpp b/llvm/lib/Target/X86/X86ISelLoweringCall.cpp
index 37c80e27f4bd2..f1a0a0047a4fd 100644
--- a/llvm/lib/Target/X86/X86ISelLoweringCall.cpp
+++ b/llvm/lib/Target/X86/X86ISelLoweringCall.cpp
@@ -2953,6 +2953,15 @@ bool X86TargetLowering::isEligibleForSiblingCallOpt(
if (!mayTailCallThisCC(CalleeCC))
return false;
+ // preserve_nonecc uses a distinct caller/callee-saved register convention.
+ // Be conservative and avoid sibling tail calls when byval arguments are
+ // present, as they require stack argument copying with stricter layout
+ // constraints.
+ if (CalleeCC == CallingConv::PreserveNone)
+ for (const auto &Out : Outs)
+ if (Out.Flags.isByVal())
+ return false;
+
// If -tailcallopt is specified, make fastcc functions tail-callable.
MachineFunction &MF = DAG.getMachineFunction();
X86MachineFunctionInfo *FuncInfo = MF.getInfo<X86MachineFunctionInfo>();
diff --git a/llvm/test/CodeGen/X86/preserve_nonecc_call.ll b/llvm/test/CodeGen/X86/preserve_nonecc_call.ll
index 500ebb139811a..df1ef74b86ae3 100644
--- a/llvm/test/CodeGen/X86/preserve_nonecc_call.ll
+++ b/llvm/test/CodeGen/X86/preserve_nonecc_call.ll
@@ -55,6 +55,24 @@ define preserve_nonecc void @caller2(ptr %a) {
ret void
}
+%S = type { [3 x i64] }
+declare preserve_nonecc void @callee_byval(ptr byval(%S) align 8)
+
+; Preserve_none caller with a byval argument should not be lowered as a
+; sibling tail call on X86.
+define preserve_nonecc void @caller_byval(ptr %a) {
+; CHECK-LABEL: caller_byval:
+; CHECK: # %bb.0:
+; CHECK-NEXT: pushq %rax
+; CHECK-NEXT: .cfi_def_cfa_offset 16
+; CHECK-NEXT: callq callee_byval at PLT
+; CHECK-NEXT: popq %rax
+; CHECK-NEXT: .cfi_def_cfa_offset 8
+; CHECK-NEXT: retq
+ tail call preserve_nonecc void @callee_byval(ptr byval(%S) align 8 %a)
+ ret void
+}
+
; Preserve_none function can use more registers to pass parameters.
declare preserve_nonecc i64 @callee_with_many_param2(i64 %a1, i64 %a2, i64 %a3, i64 %a4, i64 %a5, i64 %a6, i64 %a7, i64 %a8, i64 %a9, i64 %a10, i64 %a11)
define preserve_nonecc i64 @callee_with_many_param(i64 %a1, i64 %a2, i64 %a3, i64 %a4, i64 %a5, i64 %a6, i64 %a7, i64 %a8, i64 %a9, i64 %a10, i64 %a11, i64 %a12) {
``````````
</details>
https://github.com/llvm/llvm-project/pull/192300
More information about the llvm-commits
mailing list