[llvm] [X86] Fix TC_RETURN operand type for the x32 ABI (PR #228281)

Craig Topper via llvm-commits llvm-commits at lists.llvm.org
Thu Oct 1 17:05:30 PDT 2026


https://github.com/topperc updated https://github.com/llvm/llvm-project/pull/228281

>From 2472b79254593791856aa89700883fd8aad1f351 Mon Sep 17 00:00:00 2001
From: Craig Topper <craig.topper at sifive.com>
Date: Thu, 1 Oct 2026 16:41:57 -0700
Subject: [PATCH 1/2] [X86] Fix TC_RETURN operand type for the x32 ABI

SDT_X86TCRET constrained the tail-call target operand with SDTCisPtrTy<0>,
which resolves to the data layout's 32-bit pointer type on the x32 ABI.
However the operand is actually a GPR value that is zero-extended to 64
bits on x32, since calls still execute in 64-bit mode even though pointers
are 32-bit there; this mismatch crashed instruction selection for tail
calls through a register or folded load on x32 (e.g. call-structfp.ll).

Relax SDT_X86TCRET to SDTCisInt<0>, and update the two load-folding
patterns in X86InstrCompiler.td whose inferred type depended on the old
constraint to explicitly annotate i32/i64.

Add an X86-specific verifyTargetNode check for TC_RETURN/
TC_RETURN_GLOBALADDR that reinstates the precise expected width: i32 for a
direct call to a GlobalAddress/ExternalSymbol under the x32 ABI (and in
plain 32-bit mode), i64 for the same under LP64, and otherwise i64 iff the
subtarget runs in 64-bit mode (including x32) or i32 otherwise.

Co-Authored-By: Claude Sonnet 5.5 <noreply at anthropic.com>
---
 llvm/lib/Target/X86/X86InstrCompiler.td     |  6 ++--
 llvm/lib/Target/X86/X86InstrFragments.td    |  5 ++-
 llvm/lib/Target/X86/X86SelectionDAGInfo.cpp | 34 +++++++++++++++++++++
 3 files changed, 41 insertions(+), 4 deletions(-)

diff --git a/llvm/lib/Target/X86/X86InstrCompiler.td b/llvm/lib/Target/X86/X86InstrCompiler.td
index 45619663c45cb61..0965a011e381646 100644
--- a/llvm/lib/Target/X86/X86InstrCompiler.td
+++ b/llvm/lib/Target/X86/X86InstrCompiler.td
@@ -1410,7 +1410,7 @@ def : Pat<(X86tcret GR32:$dst, timm:$off),
 // FIXME: This is disabled for 32-bit PIC mode because the global base
 // register which is part of the address mode may be assigned a
 // callee-saved register.
-def : Pat<(X86tcret_enough_regs (load addr:$dst), timm:$off),
+def : Pat<(X86tcret_enough_regs (i32 (load addr:$dst)), timm:$off),
           (TCRETURNmi addr:$dst, timm:$off)>,
           Requires<[Not64BitMode, IsNotPIC, NotUseIndirectThunkCalls]>;
 
@@ -1436,11 +1436,11 @@ def : Pat<(X86tcret ptr_rc_tailcall:$dst, timm:$off),
 
 // Don't fold loads into X86tcret requiring too many regs.
 // There wouldn't be enough scratch registers for base+index.
-def : Pat<(X86tcret_enough_regs (load addr:$dst), timm:$off),
+def : Pat<(X86tcret_enough_regs (i64 (load addr:$dst)), timm:$off),
           (TCRETURNmi64 addr:$dst, timm:$off)>,
           Requires<[In64BitMode, IsNotWin64CCFunc, NotUseIndirectThunkCalls]>;
 
-def : Pat<(X86tcret_enough_regs (load addr:$dst), timm:$off),
+def : Pat<(X86tcret_enough_regs (i64 (load addr:$dst)), timm:$off),
           (TCRETURN_WINmi64 addr:$dst, timm:$off)>,
           Requires<[IsWin64CCFunc, NotUseIndirectThunkCalls]>;
 
diff --git a/llvm/lib/Target/X86/X86InstrFragments.td b/llvm/lib/Target/X86/X86InstrFragments.td
index 383e713f93810d3..11fd86f1f55f218 100644
--- a/llvm/lib/Target/X86/X86InstrFragments.td
+++ b/llvm/lib/Target/X86/X86InstrFragments.td
@@ -120,7 +120,10 @@ def SDT_X86PROBED_ALLOCA : SDTypeProfile<1, 1, [SDTCisVT<0, iPTR>, SDTCisVT<1, i
 
 def SDT_X86EHRET : SDTypeProfile<0, 1, [SDTCisInt<0>]>;
 
-def SDT_X86TCRET : SDTypeProfile<0, 2, [SDTCisPtrTy<0>, SDTCisVT<1, i32>]>;
+// The tail-call target is an integer, not necessarily a pointer: on the
+// x32 ABI it is a 64-bit zero-extension of a 32-bit pointer, since calls
+// still execute in 64-bit mode even though pointers are 32-bit.
+def SDT_X86TCRET : SDTypeProfile<0, 2, [SDTCisInt<0>, SDTCisVT<1, i32>]>;
 
 def SDT_X86ENQCMD : SDTypeProfile<1, 2, [SDTCisVT<0, i32>,
                                          SDTCisPtrTy<1>, SDTCisSameAs<1, 2>]>;
diff --git a/llvm/lib/Target/X86/X86SelectionDAGInfo.cpp b/llvm/lib/Target/X86/X86SelectionDAGInfo.cpp
index 8b9782be0ea41ae..00e214f521622e0 100644
--- a/llvm/lib/Target/X86/X86SelectionDAGInfo.cpp
+++ b/llvm/lib/Target/X86/X86SelectionDAGInfo.cpp
@@ -90,6 +90,40 @@ void X86SelectionDAGInfo::verifyTargetNode(const SelectionDAG &DAG,
   }
 
   SelectionDAGGenTargetInfo::verifyTargetNode(DAG, N);
+
+  switch (N->getOpcode()) {
+  default:
+    break;
+  case X86ISD::TC_RETURN:
+  case X86ISD::TC_RETURN_GLOBALADDR: {
+    // The tail-call target is an integer whose width depends on both the
+    // subtarget and on how the callee is addressed:
+    //  * A direct call to a GlobalAddress/ExternalSymbol is i32 on the
+    //    x32 ABI (as well as plain 32-bit mode) and i64 under LP64, since
+    //    TCRETURNdi/TCRETURNdi64 are selected based on IsLP64/NotLP64.
+    //  * Anything else (register, folded load, or TC_RETURN_GLOBALADDR's
+    //    RIP-relative CFGuard call) uses the register width the subtarget
+    //    executes in, i.e. i64 whenever the subtarget runs in 64-bit mode
+    //    (including x32) and i32 otherwise.
+    const X86Subtarget &Subtarget =
+        DAG.getMachineFunction().getSubtarget<X86Subtarget>();
+    SDValue Target = N->getOperand(1);
+    bool IsDirect = Target.getOpcode() == ISD::GlobalAddress ||
+                    Target.getOpcode() == ISD::TargetGlobalAddress ||
+                    Target.getOpcode() == ISD::ExternalSymbol ||
+                    Target.getOpcode() == ISD::TargetExternalSymbol;
+    bool WantI64 = IsDirect ? Subtarget.isTarget64BitLP64()
+                            : Subtarget.is64Bit();
+    EVT ExpectedVT = WantI64 ? MVT::i64 : MVT::i32;
+    EVT VT = Target.getValueType();
+    if (VT != ExpectedVT)
+      report_fatal_error("invalid node: " + Twine(N->getOperationName(&DAG)) +
+                         " operand #1 must have type " +
+                         ExpectedVT.getEVTString() + ", but has type " +
+                         VT.getEVTString());
+    break;
+  }
+  }
 }
 
 /// Returns the best type to use with repmovs/repstos depending on alignment.

>From a207f048804e7f82a53c0564053808ca026182e3 Mon Sep 17 00:00:00 2001
From: Craig Topper <craig.topper at sifive.com>
Date: Thu, 1 Oct 2026 17:05:03 -0700
Subject: [PATCH 2/2] fixup! clang-format

---
 llvm/lib/Target/X86/X86SelectionDAGInfo.cpp | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/llvm/lib/Target/X86/X86SelectionDAGInfo.cpp b/llvm/lib/Target/X86/X86SelectionDAGInfo.cpp
index 00e214f521622e0..0002d93d82ab42b 100644
--- a/llvm/lib/Target/X86/X86SelectionDAGInfo.cpp
+++ b/llvm/lib/Target/X86/X86SelectionDAGInfo.cpp
@@ -112,8 +112,8 @@ void X86SelectionDAGInfo::verifyTargetNode(const SelectionDAG &DAG,
                     Target.getOpcode() == ISD::TargetGlobalAddress ||
                     Target.getOpcode() == ISD::ExternalSymbol ||
                     Target.getOpcode() == ISD::TargetExternalSymbol;
-    bool WantI64 = IsDirect ? Subtarget.isTarget64BitLP64()
-                            : Subtarget.is64Bit();
+    bool WantI64 =
+        IsDirect ? Subtarget.isTarget64BitLP64() : Subtarget.is64Bit();
     EVT ExpectedVT = WantI64 ? MVT::i64 : MVT::i32;
     EVT VT = Target.getValueType();
     if (VT != ExpectedVT)



More information about the llvm-commits mailing list