Skip to content

[X86] Reuse unchanged incoming stack args for tail calls#208248

Open
AxXxB wants to merge 2 commits into
llvm:mainfrom
AxXxB:x86-tailcall-stack-arg-reuse
Open

[X86] Reuse unchanged incoming stack args for tail calls#208248
AxXxB wants to merge 2 commits into
llvm:mainfrom
AxXxB:x86-tailcall-stack-arg-reuse

Conversation

@AxXxB

@AxXxB AxXxB commented Jul 8, 2026

Copy link
Copy Markdown

When a musttail call forwards an incoming stack argument unchanged to the
same outgoing stack offset, X86 lowering still creates a fixed object and
stores the value back to the stack. In the common same-signature tail-call
case this materializes unnecessary stack traffic before the tail jump.

Reuse the incoming stack slot when MatchingStackOffset proves that the
outgoing argument already resides at the required offset. Keep byval
arguments on the existing copy path and limit the first reuse path to
non-negative fixed stack offsets.

The test covers unchanged stack arguments, a changed stack argument, and a
phi case where only the modified stack argument is stored.

Tested with ninja -C build -j8 llc and
build/bin/llvm-lit -q llvm/test/CodeGen/X86.

AI tool disclosure: Co-authored with OpenAI Codex.

@github-actions

github-actions Bot commented Jul 8, 2026

Copy link
Copy Markdown

Hello @AxXxB 👋

Thank you for submitting a Pull Request (PR) to the LLVM Project. Since this is your first PR, here are a few useful links covering our main contribution policies and review practices.

  • All contributions to LLVM must follow our LLVM AI Tool Use Policy. In particular, if you used AI while working on this PR, remember to add a note to the PR description.
  • The LLVM Code-Review Policy and Practices document contains practical information about the PR process, including how patches are reviewed and accepted, and who can review a PR.
  • Our LLVM Developer Policy describes our expectations for code quality, commit summaries and contains notes on our CI system.

Please reply to this message to confirm that you have read these policies, especially the LLVM AI Tool Use Policy, and that any AI tool usage has been noted in the PR description.


Frequently asked questions

How do I add reviewers?

This PR will be automatically labeled, and the relevant teams will be notified. For some parts of the project, reviewers may also be added automatically.

You can also add reviewers manually using the Reviewers section on this page. If you cannot use that section, it is probably because you do not have write permissions for the repository. In that case, you can request a review by tagging reviewers in a comment using @ followed by their GitHub username.

What if there are no comments?

If you have not received any comments on your PR after a week, you can request a review by pinging the PR with a comment such as “Ping”. The common courtesy ping rate is once a week. Please remember that you are asking for volunteer time from other developers.

Are any special GitHub settings required to contribute to LLVM?

We only require contributors to have a public email address associated with their GitHub commits, see this section of LLVM Developer Policy for details.


If you have questions, feel free to leave a comment on this PR, or ask on LLVM Discord or LLVM Discourse.

Thank you,
The LLVM Community

@AxXxB

AxXxB commented Jul 8, 2026

Copy link
Copy Markdown
Author

I have read the LLVM AI Tool Use Policy, Code-Review Policy and Practices, and Developer Policy. AI tool usage is noted in the PR description.

@llvmorg-github-actions

Copy link
Copy Markdown

@llvm/pr-subscribers-backend-x86

Author: AxXxB

Changes

When a musttail call forwards an incoming stack argument unchanged to the
same outgoing stack offset, X86 lowering still creates a fixed object and
stores the value back to the stack. In the common same-signature tail-call
case this materializes unnecessary stack traffic before the tail jump.

Reuse the incoming stack slot when MatchingStackOffset proves that the
outgoing argument already resides at the required offset. Keep byval
arguments on the existing copy path and limit the first reuse path to
non-negative fixed stack offsets.

The test covers unchanged stack arguments, a changed stack argument, and a
phi case where only the modified stack argument is stored.

Tested with ninja -C build -j8 llc and
build/bin/llvm-lit -q llvm/test/CodeGen/X86.

Assisted-by: OpenAI Codex


Full diff: https://github.com/llvm/llvm-project/pull/208248.diff

2 Files Affected:

  • (modified) llvm/lib/Target/X86/X86ISelLoweringCall.cpp (+30-2)
  • (added) llvm/test/CodeGen/X86/preserve-nonecc-tailcall-stack-args.ll (+51)
diff --git a/llvm/lib/Target/X86/X86ISelLoweringCall.cpp b/llvm/lib/Target/X86/X86ISelLoweringCall.cpp
index 819e0a023c1c5..335b76eef955b 100644
--- a/llvm/lib/Target/X86/X86ISelLoweringCall.cpp
+++ b/llvm/lib/Target/X86/X86ISelLoweringCall.cpp
@@ -2084,6 +2084,28 @@ X86TargetLowering::ByValCopyKind X86TargetLowering::ByValNeedsCopyForTailCall(
     return CopyViaTemp;
 }
 
+static bool MatchingStackOffset(SDValue Arg, unsigned Offset,
+                                ISD::ArgFlagsTy Flags, MachineFrameInfo &MFI,
+                                const MachineRegisterInfo *MRI,
+                                const X86InstrInfo *TII,
+                                const CCValAssign &VA);
+
+static bool canReuseIncomingTailCallStackArg(
+    SDValue Arg, int32_t Offset, ISD::ArgFlagsTy Flags, MachineFunction &MF,
+    const X86Subtarget &Subtarget, const CCValAssign &VA) {
+  // ByVal tailcall arguments have extra temporary-copy handling above this
+  // point. Keep the first reuse pass limited to direct scalar/vector stack
+  // arguments, matching the common musttail same-signature case.
+  if (Flags.isByVal())
+    return false;
+
+  if (Offset < 0)
+    return false;
+
+  return MatchingStackOffset(Arg, unsigned(Offset), Flags, MF.getFrameInfo(),
+                             &MF.getRegInfo(), Subtarget.getInstrInfo(), VA);
+}
+
 SDValue
 X86TargetLowering::LowerCall(TargetLowering::CallLoweringInfo &CLI,
                              SmallVectorImpl<SDValue> &InVals) const {
@@ -2532,8 +2554,14 @@ X86TargetLowering::LowerCall(TargetLowering::CallLoweringInfo &CLI,
       if (Flags.isInAlloca() || Flags.isPreallocated())
         continue;
       // Create frame index.
-      int32_t Offset = VA.getLocMemOffset()+FPDiff;
-      uint32_t OpSize = (VA.getLocVT().getSizeInBits()+7)/8;
+      int32_t Offset = VA.getLocMemOffset() + FPDiff;
+      uint32_t OpSize = (VA.getLocVT().getSizeInBits() + 7) / 8;
+
+      if (Is64Bit && isTailCall &&
+          canReuseIncomingTailCallStackArg(Arg, Offset, Flags, MF, Subtarget,
+                                           VA))
+        continue;
+
       FI = MF.getFrameInfo().CreateFixedObject(OpSize, Offset, true);
       FIN = DAG.getFrameIndex(FI, getPointerTy(DAG.getDataLayout()));
 
diff --git a/llvm/test/CodeGen/X86/preserve-nonecc-tailcall-stack-args.ll b/llvm/test/CodeGen/X86/preserve-nonecc-tailcall-stack-args.ll
new file mode 100644
index 0000000000000..939efecf417c7
--- /dev/null
+++ b/llvm/test/CodeGen/X86/preserve-nonecc-tailcall-stack-args.ll
@@ -0,0 +1,51 @@
+; RUN: llc < %s -mtriple=x86_64-unknown-linux-gnu -relocation-model=static | FileCheck %s
+
+declare preserve_nonecc void @next(i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64)
+declare preserve_nonecc void @next_with_cond(i1, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64)
+
+define preserve_nonecc void @static_tail_unchanged(i64 %a0, i64 %a1, i64 %a2, i64 %a3, i64 %a4, i64 %a5, i64 %a6, i64 %a7, i64 %a8, i64 %a9, i64 %a10, i64 %a11, i64 %a12, i64 %a13, i64 %a14, i64 %a15, i64 %a16, i64 %a17, i64 %a18, i64 %a19, i64 %a20, i64 %a21, i64 %a22, i64 %a23, i64 %a24, i64 %a25, i64 %a26, i64 %a27, i64 %a28, i64 %a29, i64 %a30, i64 %a31, i64 %a32, i64 %a33, i64 %a34, i64 %a35) {
+; CHECK-LABEL: static_tail_unchanged:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    jmp next@PLT # TAILCALL
+entry:
+  musttail call preserve_nonecc void @next(i64 %a0, i64 %a1, i64 %a2, i64 %a3, i64 %a4, i64 %a5, i64 %a6, i64 %a7, i64 %a8, i64 %a9, i64 %a10, i64 %a11, i64 %a12, i64 %a13, i64 %a14, i64 %a15, i64 %a16, i64 %a17, i64 %a18, i64 %a19, i64 %a20, i64 %a21, i64 %a22, i64 %a23, i64 %a24, i64 %a25, i64 %a26, i64 %a27, i64 %a28, i64 %a29, i64 %a30, i64 %a31, i64 %a32, i64 %a33, i64 %a34, i64 %a35)
+  ret void
+}
+
+define preserve_nonecc void @static_tail_changed(i64 %a0, i64 %a1, i64 %a2, i64 %a3, i64 %a4, i64 %a5, i64 %a6, i64 %a7, i64 %a8, i64 %a9, i64 %a10, i64 %a11, i64 %a12, i64 %a13, i64 %a14, i64 %a15, i64 %a16, i64 %a17, i64 %a18, i64 %a19, i64 %a20, i64 %a21, i64 %a22, i64 %a23, i64 %a24, i64 %a25, i64 %a26, i64 %a27, i64 %a28, i64 %a29, i64 %a30, i64 %a31, i64 %a32, i64 %a33, i64 %a34, i64 %a35) {
+; CHECK-LABEL: static_tail_changed:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    movq 168(%rsp), %r10
+; CHECK-NEXT:    incq %r10
+; CHECK-NEXT:    movq %r10, 168(%rsp)
+; CHECK-NEXT:    jmp next@PLT # TAILCALL
+entry:
+  %a32x = add i64 %a32, 1
+  musttail call preserve_nonecc void @next(i64 %a0, i64 %a1, i64 %a2, i64 %a3, i64 %a4, i64 %a5, i64 %a6, i64 %a7, i64 %a8, i64 %a9, i64 %a10, i64 %a11, i64 %a12, i64 %a13, i64 %a14, i64 %a15, i64 %a16, i64 %a17, i64 %a18, i64 %a19, i64 %a20, i64 %a21, i64 %a22, i64 %a23, i64 %a24, i64 %a25, i64 %a26, i64 %a27, i64 %a28, i64 %a29, i64 %a30, i64 %a31, i64 %a32x, i64 %a33, i64 %a34, i64 %a35)
+  ret void
+}
+
+define preserve_nonecc void @phi_tail(i1 %cond, i64 %a0, i64 %a1, i64 %a2, i64 %a3, i64 %a4, i64 %a5, i64 %a6, i64 %a7, i64 %a8, i64 %a9, i64 %a10, i64 %a11, i64 %a12, i64 %a13, i64 %a14, i64 %a15, i64 %a16, i64 %a17, i64 %a18, i64 %a19, i64 %a20, i64 %a21, i64 %a22, i64 %a23, i64 %a24, i64 %a25, i64 %a26, i64 %a27, i64 %a28, i64 %a29, i64 %a30, i64 %a31, i64 %a32, i64 %a33, i64 %a34, i64 %a35) {
+; CHECK-LABEL: phi_tail:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    movq 176(%rsp), %r10
+; CHECK-NEXT:    testb $1, %dil
+; CHECK-NEXT:    je .LBB2_2
+; CHECK:       # %bb.1:
+; CHECK-NEXT:    incq %r10
+; CHECK:       .LBB2_2:
+; CHECK-NEXT:    movq %r10, 176(%rsp)
+; CHECK-NEXT:    movzbl %dil, %edi
+; CHECK-NEXT:    jmp next_with_cond@PLT # TAILCALL
+entry:
+  br i1 %cond, label %a, label %b
+a:
+  %a32x = add i64 %a32, 1
+  br label %join
+b:
+  br label %join
+join:
+  %a32m = phi i64 [ %a32x, %a ], [ %a32, %b ]
+  musttail call preserve_nonecc void @next_with_cond(i1 %cond, i64 %a0, i64 %a1, i64 %a2, i64 %a3, i64 %a4, i64 %a5, i64 %a6, i64 %a7, i64 %a8, i64 %a9, i64 %a10, i64 %a11, i64 %a12, i64 %a13, i64 %a14, i64 %a15, i64 %a16, i64 %a17, i64 %a18, i64 %a19, i64 %a20, i64 %a21, i64 %a22, i64 %a23, i64 %a24, i64 %a25, i64 %a26, i64 %a27, i64 %a28, i64 %a29, i64 %a30, i64 %a31, i64 %a32m, i64 %a33, i64 %a34, i64 %a35)
+  ret void
+}

@github-actions

github-actions Bot commented Jul 8, 2026

Copy link
Copy Markdown

⚠️ C/C++ code formatter, clang-format found issues in your code. ⚠️

You can test this locally with the following command:
git-clang-format --diff origin/main HEAD --extensions cpp -- llvm/lib/Target/X86/X86ISelLoweringCall.cpp --diff_from_common_commit

⚠️
The reproduction instructions above might return results for more than one PR
in a stack if you are using a stacked PR workflow. You can limit the results by
changing origin/main to the base branch/commit you want to compare against.
⚠️

View the diff from clang-format here.
diff --git a/llvm/lib/Target/X86/X86ISelLoweringCall.cpp b/llvm/lib/Target/X86/X86ISelLoweringCall.cpp
index 335b76eef..d5b15253f 100644
--- a/llvm/lib/Target/X86/X86ISelLoweringCall.cpp
+++ b/llvm/lib/Target/X86/X86ISelLoweringCall.cpp
@@ -2087,12 +2087,13 @@ X86TargetLowering::ByValCopyKind X86TargetLowering::ByValNeedsCopyForTailCall(
 static bool MatchingStackOffset(SDValue Arg, unsigned Offset,
                                 ISD::ArgFlagsTy Flags, MachineFrameInfo &MFI,
                                 const MachineRegisterInfo *MRI,
-                                const X86InstrInfo *TII,
-                                const CCValAssign &VA);
+                                const X86InstrInfo *TII, const CCValAssign &VA);
 
-static bool canReuseIncomingTailCallStackArg(
-    SDValue Arg, int32_t Offset, ISD::ArgFlagsTy Flags, MachineFunction &MF,
-    const X86Subtarget &Subtarget, const CCValAssign &VA) {
+static bool canReuseIncomingTailCallStackArg(SDValue Arg, int32_t Offset,
+                                             ISD::ArgFlagsTy Flags,
+                                             MachineFunction &MF,
+                                             const X86Subtarget &Subtarget,
+                                             const CCValAssign &VA) {
   // ByVal tailcall arguments have extra temporary-copy handling above this
   // point. Keep the first reuse pass limited to direct scalar/vector stack
   // arguments, matching the common musttail same-signature case.

When a musttail call forwards an incoming stack argument unchanged to the same outgoing stack offset, X86 lowering still creates a fixed object and stores the value back to the stack. In the common same-signature tail-call case this materializes unnecessary stack traffic before the tail jump.

Reuse the incoming stack slot when MatchingStackOffset proves that the outgoing argument already resides at the required offset. Keep byval arguments on the existing copy path and limit the first reuse path to non-negative fixed stack offsets.

Assisted-by: OpenAI Codex
@AxXxB
AxXxB force-pushed the x86-tailcall-stack-arg-reuse branch from 74816cc to aae152a Compare July 8, 2026 17:45
@AxXxB

AxXxB commented Jul 8, 2026

Copy link
Copy Markdown
Author

Applied clang-format changes

@AxXxB

AxXxB commented Jul 25, 2026

Copy link
Copy Markdown
Author

PR branch updated to current main.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant