[clang] [llvm] [X86] Support reserving EDI and ESI on x86-32 (PR #186123)
via cfe-commits
cfe-commits at lists.llvm.org
Thu Mar 12 07:55:25 PDT 2026
https://github.com/zhouguangyuan0718 updated https://github.com/llvm/llvm-project/pull/186123
>From 82572f3a2129e90d70751f4f8b7bccf1b84d80c6 Mon Sep 17 00:00:00 2001
From: ZhouGuangyuan <zhouguangyuan.xian at gmail.com>
Date: Thu, 12 Mar 2026 12:33:45 +0800
Subject: [PATCH] [X86] Support reserving EDI and ESI on x86-32
Add clang driver support for -ffixed-edi and -ffixed-esi and map them to
reserve-edi/reserve-esi target features on i386.
Teach the X86 backend to treat EDI/ESI as user-reserved registers in
register lookup, reserved-register tracking, and callee-save handling,
and avoid selecting REP MOVS/REP STOS when those registers are reserved.
Add driver and codegen tests covering option handling and the resulting
code generation changes.
Signed-off-by: ZhouGuangyuan <zhouguangyuan.xian at gmail.com>
---
clang/include/clang/Options/Options.td | 4 ++
clang/lib/Basic/Targets/X86.h | 11 ++++
clang/lib/Driver/ToolChains/Arch/X86.cpp | 14 +++++
clang/test/Driver/x86-fixed-di-si-register.c | 17 ++++++
llvm/lib/Target/X86/X86.td | 4 ++
llvm/lib/Target/X86/X86FrameLowering.cpp | 2 +-
llvm/lib/Target/X86/X86ISelLowering.cpp | 8 +--
llvm/lib/Target/X86/X86RegisterInfo.cpp | 23 ++++---
llvm/lib/Target/X86/X86SelectionDAGInfo.cpp | 22 +++++--
llvm/test/CodeGen/X86/reserveDISIreg.ll | 63 ++++++++++++++++++++
10 files changed, 149 insertions(+), 19 deletions(-)
create mode 100644 clang/test/Driver/x86-fixed-di-si-register.c
create mode 100644 llvm/test/CodeGen/X86/reserveDISIreg.ll
diff --git a/clang/include/clang/Options/Options.td b/clang/include/clang/Options/Options.td
index 4bbdd9c8c0e58..27dc99b2280b6 100644
--- a/clang/include/clang/Options/Options.td
+++ b/clang/include/clang/Options/Options.td
@@ -7175,6 +7175,10 @@ def mapxf : Flag<["-"], "mapxf">, Group<m_x86_Features_Group>;
def mno_apxf : Flag<["-"], "mno-apxf">, Group<m_x86_Features_Group>;
def mapx_inline_asm_use_gpr32 : Flag<["-"], "mapx-inline-asm-use-gpr32">, Group<m_Group>,
HelpText<"Enable use of GPR32 in inline assembly for APX">;
+def ffixed_edi : Flag<["-"], "ffixed-edi">, Group<m_Group>,
+ HelpText<"Reserve the edi register (x86 only)">;
+def ffixed_esi : Flag<["-"], "ffixed-esi">, Group<m_Group>,
+ HelpText<"Reserve the esi register (x86 only)">;
foreach i = {8, 10-15} in
def ffixed_r#i : Flag<["-"], "ffixed-r"#i>, Group<m_Group>,
HelpText<"Reserve the r"#i#" register (x86_64 only)">;
diff --git a/clang/lib/Basic/Targets/X86.h b/clang/lib/Basic/Targets/X86.h
index f99bbf363458f..08e80412aca3f 100644
--- a/clang/lib/Basic/Targets/X86.h
+++ b/clang/lib/Basic/Targets/X86.h
@@ -485,6 +485,17 @@ class LLVM_LIBRARY_VISIBILITY X86_32TargetInfo : public X86TargetInfo {
return -1;
}
+ bool validateGlobalRegisterVariable(StringRef RegName, unsigned RegSize,
+ bool &HasSizeMismatch) const override {
+ if (RegName == "edi" || RegName == "esi") {
+ HasSizeMismatch = RegSize != 32;
+ return getTargetOpts().FeatureMap.lookup(("reserve-" + RegName).str());
+ }
+
+ return X86TargetInfo::validateGlobalRegisterVariable(RegName, RegSize,
+ HasSizeMismatch);
+ }
+
bool validateOperandSize(const llvm::StringMap<bool> &FeatureMap,
StringRef Constraint, unsigned Size) const override {
switch (Constraint[0]) {
diff --git a/clang/lib/Driver/ToolChains/Arch/X86.cpp b/clang/lib/Driver/ToolChains/Arch/X86.cpp
index c113da6733370..0a8ffa871630f 100644
--- a/clang/lib/Driver/ToolChains/Arch/X86.cpp
+++ b/clang/lib/Driver/ToolChains/Arch/X86.cpp
@@ -349,6 +349,20 @@ void x86::getX86TargetFeatures(const Driver &D, const llvm::Triple &Triple,
}
// Handle features corresponding to "-ffixed-X" options
+ if (Args.hasArg(options::OPT_ffixed_edi)) {
+ if (ArchType != llvm::Triple::x86)
+ D.Diag(diag::err_drv_unsupported_opt_for_target)
+ << "-ffixed-edi" << Triple.getTriple();
+ else
+ Features.push_back("+reserve-edi");
+ }
+ if (Args.hasArg(options::OPT_ffixed_esi)) {
+ if (ArchType != llvm::Triple::x86)
+ D.Diag(diag::err_drv_unsupported_opt_for_target)
+ << "-ffixed-esi" << Triple.getTriple();
+ else
+ Features.push_back("+reserve-esi");
+ }
#define RESERVE_REG(REG) \
if (Args.hasArg(options::OPT_ffixed_##REG)) \
Features.push_back("+reserve-" #REG);
diff --git a/clang/test/Driver/x86-fixed-di-si-register.c b/clang/test/Driver/x86-fixed-di-si-register.c
new file mode 100644
index 0000000000000..eff7fcf992993
--- /dev/null
+++ b/clang/test/Driver/x86-fixed-di-si-register.c
@@ -0,0 +1,17 @@
+// RUN: %clang --target=i386-unknown-linux-gnu -ffixed-edi -### %s 2> %t
+// RUN: FileCheck --check-prefix=CHECK-FIXED-EDI < %t %s
+// CHECK-FIXED-EDI: "-target-feature" "+reserve-edi"
+
+// RUN: %clang --target=i386-unknown-linux-gnu -ffixed-esi -### %s 2> %t
+// RUN: FileCheck --check-prefix=CHECK-FIXED-ESI < %t %s
+// CHECK-FIXED-ESI: "-target-feature" "+reserve-esi"
+
+// RUN: %clang --target=i386-unknown-linux-gnu -ffixed-edi -ffixed-esi -### %s 2> %t
+// RUN: FileCheck --check-prefix=CHECK-FIXED-EDI < %t %s
+// RUN: FileCheck --check-prefix=CHECK-FIXED-ESI < %t %s
+
+// RUN: not %clang --target=x86_64-unknown-linux-gnu -ffixed-edi -### %s 2>&1 | FileCheck --check-prefix=CHECK-NO-X64-EDI %s
+// CHECK-NO-X64-EDI: error: unsupported option '-ffixed-edi' for target 'x86_64-unknown-linux-gnu'
+
+// RUN: not %clang --target=x86_64-unknown-linux-gnu -ffixed-esi -### %s 2>&1 | FileCheck --check-prefix=CHECK-NO-X64-ESI %s
+// CHECK-NO-X64-ESI: error: unsupported option '-ffixed-esi' for target 'x86_64-unknown-linux-gnu'
diff --git a/llvm/lib/Target/X86/X86.td b/llvm/lib/Target/X86/X86.td
index eca763735c315..c983792fbfa4b 100644
--- a/llvm/lib/Target/X86/X86.td
+++ b/llvm/lib/Target/X86/X86.td
@@ -38,6 +38,10 @@ foreach i = {8-15} in
foreach i = {16-31} in
def FeatureReserveR#i : SubtargetFeature<"reserve-r"#i, "ReservedRReg[X86::R"#i#"]", "true",
"Reserve R"#i#", making it unavailable as a GPR">;
+def FeatureReserveEDI : SubtargetFeature<"reserve-edi", "ReservedRReg[X86::EDI]", "true",
+ "Reserve EDI, making it unavailable as a GPR">;
+def FeatureReserveESI : SubtargetFeature<"reserve-esi", "ReservedRReg[X86::ESI]", "true",
+ "Reserve ESI, making it unavailable as a GPR">;
def FeatureX87 : SubtargetFeature<"x87","HasX87", "true",
"Enable X87 float instructions">;
diff --git a/llvm/lib/Target/X86/X86FrameLowering.cpp b/llvm/lib/Target/X86/X86FrameLowering.cpp
index 8791470b0b5e1..438ba03554dfd 100644
--- a/llvm/lib/Target/X86/X86FrameLowering.cpp
+++ b/llvm/lib/Target/X86/X86FrameLowering.cpp
@@ -3211,7 +3211,7 @@ void X86FrameLowering::determineCalleeSaves(MachineFunction &MF,
BasePtr = getX86SubSuperRegister(BasePtr, 64);
SavedRegs.set(BasePtr);
}
- if (STI.is64Bit()) {
+ if (STI.hasUserReservedRegisters()) {
for (int Reg = SavedRegs.find_first(); Reg != -1;
Reg = SavedRegs.find_next(Reg)) {
if (STI.isRegisterReservedByUser(Reg)) {
diff --git a/llvm/lib/Target/X86/X86ISelLowering.cpp b/llvm/lib/Target/X86/X86ISelLowering.cpp
index bbe32aee10db5..d689836b9d8ea 100644
--- a/llvm/lib/Target/X86/X86ISelLowering.cpp
+++ b/llvm/lib/Target/X86/X86ISelLowering.cpp
@@ -28817,11 +28817,9 @@ Register X86TargetLowering::getRegisterByName(const char* RegName, LLT VT,
if (Reg)
return Reg;
- if (Subtarget.is64Bit()) {
- Reg = MatchRegisterName(RegName);
- if (!Subtarget.isRegisterReservedByUser(Reg))
- Reg = Register();
- }
+ Reg = MatchRegisterName(RegName);
+ if (!Subtarget.isRegisterReservedByUser(Reg))
+ Reg = Register();
return Reg;
}
diff --git a/llvm/lib/Target/X86/X86RegisterInfo.cpp b/llvm/lib/Target/X86/X86RegisterInfo.cpp
index 8888bf3a46a55..2286b8a870987 100644
--- a/llvm/lib/Target/X86/X86RegisterInfo.cpp
+++ b/llvm/lib/Target/X86/X86RegisterInfo.cpp
@@ -516,17 +516,24 @@ BitVector X86RegisterInfo::getReservedRegs(const MachineFunction &MF) const {
Reserved.set(X86::SSP);
auto &ST = MF.getSubtarget<X86Subtarget>();
- if (ST.is64Bit() && ST.hasUserReservedRegisters()) {
- // Set r# as reserved register if user required
- for (unsigned Reg = X86::R8; Reg <= X86::R15; ++Reg)
- if (ST.isRegisterReservedByUser(Reg))
- for (const MCPhysReg &SubReg : subregs_inclusive(Reg))
- Reserved.set(SubReg);
- if (ST.hasEGPR())
- for (unsigned Reg = X86::R16; Reg <= X86::R31; ++Reg)
+ if (ST.hasUserReservedRegisters()) {
+ if (ST.is64Bit()) {
+ // Set r# as reserved register if user required.
+ for (unsigned Reg = X86::R8; Reg <= X86::R15; ++Reg)
if (ST.isRegisterReservedByUser(Reg))
for (const MCPhysReg &SubReg : subregs_inclusive(Reg))
Reserved.set(SubReg);
+ if (ST.hasEGPR())
+ for (unsigned Reg = X86::R16; Reg <= X86::R31; ++Reg)
+ if (ST.isRegisterReservedByUser(Reg))
+ for (const MCPhysReg &SubReg : subregs_inclusive(Reg))
+ Reserved.set(SubReg);
+ } else {
+ for (unsigned Reg : {X86::EDI, X86::ESI})
+ if (ST.isRegisterReservedByUser(Reg))
+ for (const MCPhysReg &SubReg : sub_and_superregs_inclusive(Reg))
+ Reserved.set(SubReg);
+ }
}
// Set the instruction pointer register and its aliases as reserved.
diff --git a/llvm/lib/Target/X86/X86SelectionDAGInfo.cpp b/llvm/lib/Target/X86/X86SelectionDAGInfo.cpp
index dff8832e851d9..619c57d08980e 100644
--- a/llvm/lib/Target/X86/X86SelectionDAGInfo.cpp
+++ b/llvm/lib/Target/X86/X86SelectionDAGInfo.cpp
@@ -264,10 +264,18 @@ SDValue X86SelectionDAGInfo::EmitTargetCodeForMemset(
SelectionDAG &DAG, const SDLoc &dl, SDValue Chain, SDValue Dst, SDValue Val,
SDValue Size, Align Alignment, bool isVolatile, bool AlwaysInline,
MachinePointerInfo DstPtrInfo) const {
+ const X86Subtarget &Subtarget =
+ DAG.getMachineFunction().getSubtarget<X86Subtarget>();
+
// If to a segment-relative address space, use the default lowering.
if (DstPtrInfo.getAddrSpace() >= 256)
return SDValue();
+ // REP STOS uses EDI on x86-32. Fall back if the user reserved EDI, so the
+ // generic expander can avoid emitting REP STOS.
+ if (!Subtarget.is64Bit() && Subtarget.isRegisterReservedByUser(X86::EDI))
+ return SDValue();
+
// If the base register might conflict with our physical registers, bail out.
const MCPhysReg ClobberSet[] = {X86::RCX, X86::RAX, X86::RDI,
X86::ECX, X86::EAX, X86::EDI};
@@ -278,8 +286,6 @@ SDValue X86SelectionDAGInfo::EmitTargetCodeForMemset(
if (!ConstantSize)
return SDValue();
- const X86Subtarget &Subtarget =
- DAG.getMachineFunction().getSubtarget<X86Subtarget>();
return emitConstantSizeRepstos(
DAG, Subtarget, dl, Chain, Dst, Val, ConstantSize->getZExtValue(),
Size.getValueType(), Alignment, isVolatile, AlwaysInline, DstPtrInfo);
@@ -378,10 +384,19 @@ SDValue X86SelectionDAGInfo::EmitTargetCodeForMemcpy(
SelectionDAG &DAG, const SDLoc &dl, SDValue Chain, SDValue Dst, SDValue Src,
SDValue Size, Align Alignment, bool isVolatile, bool AlwaysInline,
MachinePointerInfo DstPtrInfo, MachinePointerInfo SrcPtrInfo) const {
+ const X86Subtarget &Subtarget =
+ DAG.getMachineFunction().getSubtarget<X86Subtarget>();
+
// If to a segment-relative address space, use the default lowering.
if (DstPtrInfo.getAddrSpace() >= 256 || SrcPtrInfo.getAddrSpace() >= 256)
return SDValue();
+ // REP MOVS uses EDI/ESI on x86-32. Fall back if the user reserved either
+ // string-op register, so the generic expander can avoid emitting REP MOVS.
+ if (!Subtarget.is64Bit() && (Subtarget.isRegisterReservedByUser(X86::EDI) ||
+ Subtarget.isRegisterReservedByUser(X86::ESI)))
+ return SDValue();
+
// If the base registers conflict with our physical registers, use the default
// lowering.
const MCPhysReg ClobberSet[] = {X86::RCX, X86::RSI, X86::RDI,
@@ -389,9 +404,6 @@ SDValue X86SelectionDAGInfo::EmitTargetCodeForMemcpy(
if (isBaseRegConflictPossible(DAG, ClobberSet))
return SDValue();
- const X86Subtarget &Subtarget =
- DAG.getMachineFunction().getSubtarget<X86Subtarget>();
-
// If enabled and available, use fast short rep mov.
if (UseFSRMForMemcpy && Subtarget.hasFSRM())
return emitRepmovs(Subtarget, DAG, dl, Chain, Dst, Src, Size, MVT::i8);
diff --git a/llvm/test/CodeGen/X86/reserveDISIreg.ll b/llvm/test/CodeGen/X86/reserveDISIreg.ll
new file mode 100644
index 0000000000000..0d44e37d6ab07
--- /dev/null
+++ b/llvm/test/CodeGen/X86/reserveDISIreg.ll
@@ -0,0 +1,63 @@
+;; Check if manually reserved EDI/ESI are always excluded from being saved by
+;; the function prolog/epilog, as per GCC behavior. REP MOVS should not be
+;; selected when either register is reserved, while REP STOS only depends on
+;; EDI.
+
+; RUN: llc < %s -mtriple=i386-unknown-linux-gnu -verify-machineinstrs | FileCheck %s
+
+declare void @llvm.memcpy.p0.p0.i32(ptr nocapture writeonly, ptr nocapture readonly, i32, i1 immarg)
+declare void @llvm.memset.p0.i32(ptr nocapture writeonly, i8, i32, i1 immarg)
+
+define void @tedi() "target-features"="+reserve-edi" {
+; CHECK-LABEL: tedi:
+; CHECK: # %bb.0:
+; CHECK-NOT: pushl %edi
+; CHECK-NEXT: movl $256, %edi
+; CHECK-NEXT: #APP
+; CHECK-NEXT: #NO_APP
+; CHECK-NOT: popl %edi
+; CHECK-NEXT: retl
+ call i32 asm sideeffect "", "={edi},{edi}"(i32 256)
+ ret void
+}
+
+define void @tesi() "target-features"="+reserve-esi" {
+; CHECK-LABEL: tesi:
+; CHECK: # %bb.0:
+; CHECK-NOT: pushl %esi
+; CHECK-NEXT: movl $256, %esi
+; CHECK-NEXT: #APP
+; CHECK-NEXT: #NO_APP
+; CHECK-NOT: popl %esi
+; CHECK-NEXT: retl
+ call i32 asm sideeffect "", "={esi},{esi}"(i32 256)
+ ret void
+}
+
+define void @copy_reserved_edi(ptr %dst, ptr %src) minsize "target-features"="+reserve-edi" {
+; CHECK-LABEL: copy_reserved_edi:
+; CHECK-NOT: rep;movs
+ call void @llvm.memcpy.p0.p0.i32(ptr align 4 %dst, ptr align 4 %src, i32 128, i1 false)
+ ret void
+}
+
+define void @copy_reserved_esi(ptr %dst, ptr %src) minsize "target-features"="+reserve-esi" {
+; CHECK-LABEL: copy_reserved_esi:
+; CHECK-NOT: rep;movs
+ call void @llvm.memcpy.p0.p0.i32(ptr align 4 %dst, ptr align 4 %src, i32 128, i1 false)
+ ret void
+}
+
+define void @set_reserved_edi(ptr %dst) minsize "target-features"="+reserve-edi" {
+; CHECK-LABEL: set_reserved_edi:
+; CHECK-NOT: rep;stos
+ call void @llvm.memset.p0.i32(ptr align 4 %dst, i8 0, i32 128, i1 false)
+ ret void
+}
+
+define void @set_reserved_esi(ptr %dst) minsize "target-features"="+reserve-esi" {
+; CHECK-LABEL: set_reserved_esi:
+; CHECK: rep;stosl
+ call void @llvm.memset.p0.i32(ptr align 4 %dst, i8 0, i32 128, i1 false)
+ ret void
+}
More information about the cfe-commits
mailing list