[llvm] IPRA:Fix crash and verification failures for PLT calls (PR #196917)
via llvm-commits
llvm-commits at lists.llvm.org
Mon May 11 03:08:38 PDT 2026
https://github.com/vigbalu created https://github.com/llvm/llvm-project/pull/196917
This patch fixes crashes and verification issues caused by IPRA.
When IPRA is enabled, caller-saved registers used across function calls without save/restore. The IPRA pass look-up for the unused caller-saved registers in the callee before using them in the caller.
However, for PLT calls, the register usage in the PLT stubs and the resolver function is not accounted for.
This can lead to registers being overwritten during the resolver call, causing the original values of unsaved caller-saved registers to be lost.
To address this, the patch ensures that if a call instruction has the PLT flag, the pass exits early and avoids applying any IPRA-related modifications.
Fixes https://github.com/llvm/llvm-project/issues/184098
>From 7c7e9991a7cb00120aed8e770411ceccd4d532dc Mon Sep 17 00:00:00 2001
From: Vignesh Balasubramanian <vigbalas at amd.com>
Date: Mon, 11 May 2026 14:36:37 +0530
Subject: [PATCH] IPRA:Fix crash and verification failures for PLT calls
This patch fixes crashes and verification issues caused by IPRA.
When IPRA is enabled, caller-saved registers used across function calls without
save/restore. The IPRA pass look-up for the unused caller-saved registers in
the callee before using them in the caller.
However, for PLT calls, the register usage in the PLT stubs and
the resolver function is not accounted for.
This can lead to registers being overwritten during the resolver call, causing
the original values of unsaved caller-saved registers to be lost.
To address this, the patch ensures that if a call instruction has the PLT flag,
the pass exits early and avoids applying any IPRA-related modifications.
Fixes #184098
---
llvm/include/llvm/CodeGen/TargetInstrInfo.h | 6 ++
llvm/lib/CodeGen/RegUsageInfoPropagate.cpp | 21 +++++
llvm/lib/Target/M68k/M68kInstrInfo.cpp | 5 ++
llvm/lib/Target/M68k/M68kInstrInfo.h | 2 +
llvm/lib/Target/PowerPC/PPCInstrInfo.cpp | 7 ++
llvm/lib/Target/PowerPC/PPCInstrInfo.h | 2 +
llvm/lib/Target/RISCV/RISCVInstrInfo.cpp | 6 ++
llvm/lib/Target/RISCV/RISCVInstrInfo.h | 2 +
llvm/lib/Target/X86/X86InstrInfo.cpp | 5 ++
llvm/lib/Target/X86/X86InstrInfo.h | 2 +
llvm/test/CodeGen/X86/ipra-plt-check.ll | 91 +++++++++++++++++++++
llvm/test/CodeGen/X86/ipra-transform.ll | 4 +-
12 files changed, 151 insertions(+), 2 deletions(-)
create mode 100644 llvm/test/CodeGen/X86/ipra-plt-check.ll
diff --git a/llvm/include/llvm/CodeGen/TargetInstrInfo.h b/llvm/include/llvm/CodeGen/TargetInstrInfo.h
index fef1e4fc85786..e2c9681577fd8 100644
--- a/llvm/include/llvm/CodeGen/TargetInstrInfo.h
+++ b/llvm/include/llvm/CodeGen/TargetInstrInfo.h
@@ -2139,6 +2139,12 @@ class LLVM_ABI TargetInstrInfo : public MCInstrInfo {
return {};
}
+ /// Return true if \p TF encodes a PLT-related target flag for this target.
+ ///
+ /// This allows target-independent MIR/CodeGen passes to query PLT semantics
+ /// without depending on target-specific MO_* enum values.
+ virtual bool isPLTTargetFlag(unsigned /*TF*/) const { return false; }
+
/// Return an array that contains the bitmask target flag values and their
/// names.
///
diff --git a/llvm/lib/CodeGen/RegUsageInfoPropagate.cpp b/llvm/lib/CodeGen/RegUsageInfoPropagate.cpp
index 0e7757e843666..015fb84363a55 100644
--- a/llvm/lib/CodeGen/RegUsageInfoPropagate.cpp
+++ b/llvm/lib/CodeGen/RegUsageInfoPropagate.cpp
@@ -26,6 +26,7 @@
#include "llvm/CodeGen/MachineRegisterInfo.h"
#include "llvm/CodeGen/Passes.h"
#include "llvm/CodeGen/RegisterUsageInfo.h"
+#include "llvm/CodeGen/TargetInstrInfo.h"
#include "llvm/IR/Analysis.h"
#include "llvm/IR/Module.h"
#include "llvm/InitializePasses.h"
@@ -90,10 +91,30 @@ INITIALIZE_PASS_END(RegUsageInfoPropagationLegacy, "reg-usage-propagation",
char RegUsageInfoPropagationLegacy::ID = 0;
+static bool callHasPLTTargetFlag(const MachineInstr &MI,
+ const MachineOperand &MO) {
+ const MachineFunction *MF = MI.getMF();
+ if (!MF)
+ return false;
+
+ const TargetInstrInfo *TII = MF->getSubtarget().getInstrInfo();
+ if (!TII)
+ return false;
+
+ if (TII->isPLTTargetFlag(MO.getTargetFlags()))
+ return true;
+
+ return false;
+}
+
// Assumes call instructions have a single reference to a function.
static const Function *findCalledFunction(const Module &M,
const MachineInstr &MI) {
+
for (const MachineOperand &MO : MI.operands()) {
+ if (callHasPLTTargetFlag(MI, MO))
+ return nullptr;
+
if (MO.isGlobal())
return dyn_cast<const Function>(MO.getGlobal());
diff --git a/llvm/lib/Target/M68k/M68kInstrInfo.cpp b/llvm/lib/Target/M68k/M68kInstrInfo.cpp
index 3a9f88269f42c..bfa8270bdbcd8 100644
--- a/llvm/lib/Target/M68k/M68kInstrInfo.cpp
+++ b/llvm/lib/Target/M68k/M68kInstrInfo.cpp
@@ -902,6 +902,11 @@ M68kInstrInfo::getSerializableDirectMachineOperandTargetFlags() const {
return ArrayRef(TargetFlags);
}
+bool M68kInstrInfo::isPLTTargetFlag(unsigned TF) const {
+ auto Flags = decomposeMachineOperandsTargetFlags(TF);
+ return Flags.first == M68kII::MO_PLT;
+}
+
#undef DEBUG_TYPE
#define DEBUG_TYPE "m68k-create-global-base-reg"
diff --git a/llvm/lib/Target/M68k/M68kInstrInfo.h b/llvm/lib/Target/M68k/M68kInstrInfo.h
index c88c2f53f5f89..b2fd58b301f42 100644
--- a/llvm/lib/Target/M68k/M68kInstrInfo.h
+++ b/llvm/lib/Target/M68k/M68kInstrInfo.h
@@ -333,6 +333,8 @@ class M68kInstrInfo : public M68kGenInstrInfo {
ArrayRef<std::pair<unsigned, const char *>>
getSerializableDirectMachineOperandTargetFlags() const override;
+
+ bool isPLTTargetFlag(unsigned TF) const override;
};
} // namespace llvm
diff --git a/llvm/lib/Target/PowerPC/PPCInstrInfo.cpp b/llvm/lib/Target/PowerPC/PPCInstrInfo.cpp
index 59d18de06b2e0..83b1371ba621c 100644
--- a/llvm/lib/Target/PowerPC/PPCInstrInfo.cpp
+++ b/llvm/lib/Target/PowerPC/PPCInstrInfo.cpp
@@ -3091,6 +3091,13 @@ PPCInstrInfo::getSerializableDirectMachineOperandTargetFlags() const {
return ArrayRef(TargetFlags);
}
+bool PPCInstrInfo::isPLTTargetFlag(unsigned TF) const {
+ auto Flags = decomposeMachineOperandsTargetFlags(TF);
+ if (Flags.first == PPCII::MO_PLT)
+ return true;
+ return (Flags.second & PPCII::MO_PLT) != 0;
+}
+
// Expand VSX Memory Pseudo instruction to either a VSX or a FP instruction.
// The VSX versions have the advantage of a full 64-register target whereas
// the FP ones have the advantage of lower latency and higher throughput. So
diff --git a/llvm/lib/Target/PowerPC/PPCInstrInfo.h b/llvm/lib/Target/PowerPC/PPCInstrInfo.h
index c8a747ac829ec..d67b691aafd0f 100644
--- a/llvm/lib/Target/PowerPC/PPCInstrInfo.h
+++ b/llvm/lib/Target/PowerPC/PPCInstrInfo.h
@@ -711,6 +711,8 @@ class PPCInstrInfo : public PPCGenInstrInfo {
ArrayRef<std::pair<unsigned, const char *>>
getSerializableDirectMachineOperandTargetFlags() const override;
+ bool isPLTTargetFlag(unsigned TF) const override;
+
// Expand VSX Memory Pseudo instruction to either a VSX or a FP instruction.
bool expandVSXMemPseudo(MachineInstr &MI) const;
diff --git a/llvm/lib/Target/RISCV/RISCVInstrInfo.cpp b/llvm/lib/Target/RISCV/RISCVInstrInfo.cpp
index 93512842712df..a17f57e3c9c9a 100644
--- a/llvm/lib/Target/RISCV/RISCVInstrInfo.cpp
+++ b/llvm/lib/Target/RISCV/RISCVInstrInfo.cpp
@@ -3604,6 +3604,12 @@ RISCVInstrInfo::getSerializableDirectMachineOperandTargetFlags() const {
{MO_TLSDESC_CALL, "riscv-tlsdesc-call"}};
return ArrayRef(TargetFlags);
}
+
+bool RISCVInstrInfo::isPLTTargetFlag(unsigned TF) const {
+ auto Flags = decomposeMachineOperandsTargetFlags(TF);
+ return Flags.first == RISCVII::MO_PLT;
+}
+
bool RISCVInstrInfo::isFunctionSafeToOutlineFrom(
MachineFunction &MF, bool OutlineFromLinkOnceODRs) const {
const Function &F = MF.getFunction();
diff --git a/llvm/lib/Target/RISCV/RISCVInstrInfo.h b/llvm/lib/Target/RISCV/RISCVInstrInfo.h
index 273ed5248f7cd..9be7f1f50ada6 100644
--- a/llvm/lib/Target/RISCV/RISCVInstrInfo.h
+++ b/llvm/lib/Target/RISCV/RISCVInstrInfo.h
@@ -222,6 +222,8 @@ class RISCVInstrInfo : public RISCVGenInstrInfo {
ArrayRef<std::pair<unsigned, const char *>>
getSerializableDirectMachineOperandTargetFlags() const override;
+ bool isPLTTargetFlag(unsigned TF) const override;
+
// Return true if the function can safely be outlined from.
bool isFunctionSafeToOutlineFrom(MachineFunction &MF,
bool OutlineFromLinkOnceODRs) const override;
diff --git a/llvm/lib/Target/X86/X86InstrInfo.cpp b/llvm/lib/Target/X86/X86InstrInfo.cpp
index 083024765109c..8dea66332005a 100644
--- a/llvm/lib/Target/X86/X86InstrInfo.cpp
+++ b/llvm/lib/Target/X86/X86InstrInfo.cpp
@@ -10465,6 +10465,11 @@ X86InstrInfo::getSerializableDirectMachineOperandTargetFlags() const {
return ArrayRef(TargetFlags);
}
+bool X86InstrInfo::isPLTTargetFlag(unsigned TF) const {
+ auto Flags = decomposeMachineOperandsTargetFlags(TF);
+ return Flags.first == X86II::MO_PLT;
+}
+
/// Constants defining how certain sequences should be outlined.
///
/// \p MachineOutlinerDefault implies that the function is called with a call
diff --git a/llvm/lib/Target/X86/X86InstrInfo.h b/llvm/lib/Target/X86/X86InstrInfo.h
index cab63623613a0..69d4cc72f9e6b 100644
--- a/llvm/lib/Target/X86/X86InstrInfo.h
+++ b/llvm/lib/Target/X86/X86InstrInfo.h
@@ -626,6 +626,8 @@ class X86InstrInfo final : public X86GenInstrInfo {
ArrayRef<std::pair<unsigned, const char *>>
getSerializableDirectMachineOperandTargetFlags() const override;
+ bool isPLTTargetFlag(unsigned TF) const override;
+
std::optional<std::unique_ptr<outliner::OutlinedFunction>>
getOutliningCandidateInfo(
const MachineModuleInfo &MMI,
diff --git a/llvm/test/CodeGen/X86/ipra-plt-check.ll b/llvm/test/CodeGen/X86/ipra-plt-check.ll
new file mode 100644
index 0000000000000..0b2dbe0e00f05
--- /dev/null
+++ b/llvm/test/CodeGen/X86/ipra-plt-check.ll
@@ -0,0 +1,91 @@
+; REQUIRES: x86-registered-target
+;
+; Dump MIR after the IPRA register-usage collector (RegUsageInfoCollector), which
+; runs late in codegen (after regalloc, prolog/epilog, pre-emit hooks, etc.).
+; Requires -enable-ipra so that pass is scheduled. RegUsageInfoPropagation uses
+; X86InstrInfo::isPLTTargetFlag() on call operands; MIR must still show
+; target-flags(x86-plt) on PLT calls.
+;
+; Note: -print-regusage writes to stderr in PhysicalRegisterUsageInfo::doFinalization
+; and is unrelated to this stdout MIR dump; use a separate llc run if you need it.
+;
+; RUN: llc -mtriple=x86_64-unknown-linux-gnu -enable-ipra -mattr=-avx,-avx2,-avx512f \
+; RUN: -stop-after=RegUsageInfoCollector %s -o - | FileCheck %s
+
+target triple = "x86_64-unknown-linux-gnu"
+
+!llvm.module.flags = !{!0}
+!0 = !{ i32 7, !"PIC Level", i32 2 }
+
+$_ZN7DerivedC2Eiii = comdat any
+%struct.VAR = type { i32, i32, i8 }
+
+ at goal = local_unnamed_addr global i32 0, align 4
+ at gstruct_i = global %struct.VAR { i32 10, i32 20, i8 97 }, align 4
+ at gstruct = local_unnamed_addr global ptr @gstruct_i, align 8
+
+; dso_local so the tail call from @_Z6createiii lowers without PLT; contrasts with
+; the comdat constructor's call to @_Z6createiii (not dso_local).
+define dso_local void @_Z5dummyiii(i32 noundef %x, i32 noundef %y, i32 noundef %z) local_unnamed_addr #3 {
+entry:
+ %add = add nsw i32 %z, %x
+ store i32 %add, ptr @gstruct_i, align 4, !tbaa !12
+ %add1 = add nsw i32 %y, 10
+ store i32 %add1, ptr getelementptr inbounds (%struct.VAR, ptr @gstruct_i, i64 0, i32 1), align 4, !tbaa !17
+ store ptr @gstruct_i, ptr @gstruct, align 8, !tbaa !18
+ ret void
+}
+
+; Uses XMM and R8 in assembly (clobbers listed for IPRA / regmask tests).
+define dso_local void @dummy_with_xmm_r8() local_unnamed_addr #8 {
+entry:
+ call void asm sideeffect "movaps %xmm1, %xmm0\0A\09addq $$0, %r8", "~{xmm0},~{xmm1},~{r8},~{dirflag},~{fpsr},~{flags}"()
+ ret void
+}
+
+; Ensure no PLT flag is call instruction but CustomRegMask from IPRA pass
+; CHECK-NOT: target-flags(x86-plt){{.*}}@_Z5dummyiii
+; CHECK: CALL64pcrel32 @_Z5dummyiii{{.*}}CustomRegMask
+
+; Ensure that Regmask doesnt contain XMM0, XMM1 and R8 used by dummy_with_xmm_r8
+; CHECK-NOT: target-flags(x86-plt){{.*}}@dummy_with_xmm_r8
+; CHECK-NOT: {{,(\\$xmm0|\\$xmm1)[,)]}}
+; CHECK-NOT: {{,\\$r8[,)]}}
+
+define void @_Z6createiii(i32 noundef %x, i32 noundef %y, i32 noundef %z) local_unnamed_addr #4 {
+entry:
+ call void @_Z5dummyiii(i32 noundef %x, i32 noundef %y, i32 noundef %z)
+ call void @dummy_with_xmm_r8()
+ %add = add nsw i32 %y, %x
+ %add1 = add nsw i32 %add, %z
+ store i32 %add1, ptr @goal, align 4, !tbaa !21
+ ret void
+}
+
+; Ensure PLT flag is present on call instruction and no CustomRegMask from IPRA pass.
+; CHECK: target-flags(x86-plt) @_Z6createiii
+; CHECK-NOT: CustomRegMask
+
+define linkonce_odr void @_ZN7DerivedC2Eiii(ptr noundef nonnull align 1 dereferenceable(1) %this,
+ i32 noundef %x, i32 noundef %y, i32 noundef %z) unnamed_addr #7 comdat {
+entry:
+ tail call void @_Z6createiii(i32 noundef %x, i32 noundef %y, i32 noundef %z)
+ ret void
+}
+
+attributes #3 = { mustprogress nofree noinline norecurse nosync nounwind willreturn memory(write, argmem: none, inaccessiblemem: none) uwtable "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cmov,+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" }
+attributes #4 = { mustprogress nofree noinline norecurse nosync nounwind willreturn memory(write, inaccessiblemem: none) uwtable "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cmov,+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" }
+attributes #7 = { noinline uwtable "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cmov,+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" }
+attributes #8 = { noinline nounwind uwtable "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cmov,+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" }
+
+!12 = !{!13, !16, i64 0, i64 4}
+!13 = !{!14, i64 12, !"struct at _ZTS3VAR", !16, i64 0, i64 4, !16, i64 4, i64 4, !14, i64 8, i64 1}
+!14 = !{!15, i64 1, !"omnipotent char"}
+!15 = !{!"Simple C++ TBAA"}
+!16 = !{!14, i64 4, !"int"}
+!17 = !{!13, !16, i64 4, i64 4}
+!18 = !{!19, !19, i64 0, i64 8}
+!19 = !{!20, i64 8, !"pointer at _ZTSP3VAR"}
+!20 = !{!14, i64 8, !"any pointer"}
+!21 = !{!16, !16, i64 0, i64 4}
+
diff --git a/llvm/test/CodeGen/X86/ipra-transform.ll b/llvm/test/CodeGen/X86/ipra-transform.ll
index fc94865c4d29a..c755adfeeccc9 100644
--- a/llvm/test/CodeGen/X86/ipra-transform.ll
+++ b/llvm/test/CodeGen/X86/ipra-transform.ll
@@ -4,7 +4,7 @@
target triple = "x86_64-unknown-unknown"
-define void @bar1() {
+define dso_local void @bar1() {
ret void
}
define preserve_allcc void @foo()#0 {
@@ -26,7 +26,7 @@ define preserve_allcc void @foo()#0 {
call void @bar2()
ret void
}
-define void @bar2() {
+define dso_local void @bar2() {
ret void
}
More information about the llvm-commits
mailing list