[X86] Support reserving EDI on x86-32 (#186123)

Which is under discussion in
https://github.com/llvm/llvm-project/issues/179036.
x86-64 support is added in
https://github.com/llvm/llvm-project/pull/180242.
Now add x86-32 support for reserving EDI via `-ffixed-edi` Update the
X86 backend to respect those reservations in register handling,
callee-save logic, and memcpy/memset lowering, and add driver/codegen
tests.

Add clang driver support for -ffixed-edi and map it to the reserve-edi
target feature on i386.

Teach the X86 backend to treat EDI as a user-reserved register in
register lookup, reserved-register tracking, and callee-save handling,
and avoid selecting REP MOVS/REP STOS when EDI is reserved.

Add driver, Sema, and codegen tests covering option handling, named
global register variables, and the resulting code generation changes.

Signed-off-by: ZhouGuangyuan <zhouguangyuan.xian@gmail.com>
This commit is contained in:
zhouguangyuan0718 2026-03-18 11:33:08 +08:00 committed by GitHub
parent 495c518b96
commit 3a1d5b5b8c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 180 additions and 21 deletions

View File

@ -7214,6 +7214,8 @@ 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)">;
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)">;

View File

@ -257,6 +257,16 @@ public:
HasSizeMismatch = RegSize != 32;
return true;
}
if (RegName.ends_with("di")) {
if (getTargetOpts().FeatureMap.lookup("reserve-edi")) {
if (RegName == "edi") {
HasSizeMismatch = RegSize != 32;
} else if (RegName == "di") {
HasSizeMismatch = RegSize != 16;
}
return true;
}
}
return false;
}
@ -815,8 +825,22 @@ public:
if (Reg64.back() == 'd' || Reg64.back() == 'w' || Reg64.back() == 'b') {
Reg64 = Reg64.substr(0, Reg64.size() - 1);
}
if (getTargetOpts().FeatureMap.lookup(("reserve-" + Reg64).str()))
if (getTargetOpts().FeatureMap.lookup(("reserve-" + Reg64).str())) {
switch (RegName.back()) {
case 'd':
HasSizeMismatch = RegSize != 32;
break;
case 'w':
HasSizeMismatch = RegSize != 16;
break;
case 'b':
HasSizeMismatch = RegSize != 8;
break;
default:
HasSizeMismatch = RegSize != 64;
}
return true;
}
// Check if the register is a 32-bit register the backend can handle.
return X86TargetInfo::validateGlobalRegisterVariable(RegName, RegSize,

View File

@ -349,6 +349,13 @@ 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");
}
#define RESERVE_REG(REG) \
if (Args.hasArg(options::OPT_ffixed_##REG)) \
Features.push_back("+reserve-" #REG);

View File

@ -0,0 +1,6 @@
// 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: 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'

View File

@ -0,0 +1,36 @@
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu %s -DX64_NORESERVE -verify=common,x64_noreserve -fsyntax-only
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu %s -DX64_RESERVE -target-feature +reserve-r11 -verify=common,x64_reserve -fsyntax-only
// RUN: %clang_cc1 -triple i386-unknown-linux-gnu %s -DX86_NORESERVE -verify=common,x86_noreserve -fsyntax-only
// RUN: %clang_cc1 -triple i386-unknown-linux-gnu %s -DX86_RESERVE -target-feature +reserve-edi -verify=common,x86_reserve -fsyntax-only
#if defined(X64_NORESERVE) || defined(X64_RESERVE)
register long long x64_rsp_ok __asm__("rsp");
register int x64_rsp_bad_size __asm__("rsp"); // common-error {{size of register 'rsp' does not match variable size}}
register long long x64_rbp_ok __asm__("rbp");
#endif
#ifdef X64_NORESERVE
register long long x64_r11_noreserve __asm__("r11"); // x64_noreserve-error {{register 'r11' unsuitable for global register variables on this target}}
#endif
#ifdef X64_RESERVE
register long long x64_r11_ok __asm__("r11");
register int x64_r11d_ok __asm__("r11d");
register short x64_r11w_ok __asm__("r11w");
register char x64_r11b_ok __asm__("r11b");
#endif
#if defined(X86_NORESERVE) || defined(X86_RESERVE)
register int x86_esp_ok __asm__("esp");
register long long x86_esp_bad_size __asm__("esp"); // common-error {{size of register 'esp' does not match variable size}}
register int x86_ebp_ok __asm__("ebp");
#endif
#ifdef X86_NORESERVE
register int x86_edi_noreserve __asm__("edi"); // x86_noreserve-error {{register 'edi' unsuitable for global register variables on this target}}
#endif
#ifdef X86_RESERVE
register int x86_edi_ok __asm__("edi");
register char x86_edi_bad_size __asm__("edi"); // x86_reserve-error {{size of register 'edi' does not match variable size}}
#endif

View File

@ -38,6 +38,8 @@ 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 FeatureX87 : SubtargetFeature<"x87","HasX87", "true",
"Enable X87 float instructions">;

View File

@ -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)) {

View File

@ -28834,11 +28834,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;
}

View File

@ -516,17 +516,23 @@ 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 {
if (ST.isRegisterReservedByUser(X86::EDI))
for (const MCPhysReg &SubReg : sub_and_superregs_inclusive(X86::EDI))
Reserved.set(SubReg);
}
}
// Set the instruction pointer register and its aliases as reserved.

View File

@ -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,18 @@ 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 only when EDI is
// reserved so the generic expander can avoid emitting REP MOVS.
if (!Subtarget.is64Bit() && Subtarget.isRegisterReservedByUser(X86::EDI))
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 +403,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);

View File

@ -303,6 +303,8 @@ void X86Subtarget::initSubtargetFeatures(StringRef CPU, StringRef TuneCPU,
PreferVectorWidth = 128;
else if (Prefer256Bit)
PreferVectorWidth = 256;
HasUserReservedRegisters = ReservedRReg.any();
}
X86Subtarget &X86Subtarget::initializeSubtargetDependencies(StringRef CPU,

View File

@ -102,6 +102,8 @@ class X86Subtarget final : public X86GenSubtargetInfo {
/// Required vector width from function attribute.
unsigned RequiredVectorWidth;
bool HasUserReservedRegisters;
X86SelectionDAGInfo TSInfo;
// Ordering here is important. X86InstrInfo initializes X86RegisterInfo which
// X86TargetLowering needs.
@ -162,7 +164,7 @@ public:
bool isRegisterReservedByUser(Register i) const override {
return ReservedRReg[i.id()];
}
bool hasUserReservedRegisters() const { return ReservedRReg.any(); }
bool hasUserReservedRegisters() const { return HasUserReservedRegisters; }
private:
/// Initialize the full set of dependencies so we can use an initializer

View File

@ -0,0 +1,63 @@
;; Check if manually reserved EDI is always excluded from being saved by the
;; function prolog/epilog, as per GCC behavior, and that REP MOVS/STOS are not
;; selected when EDI is reserved on x86-32.
; 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-NEXT: movl $256, %edi
; CHECK-NEXT: #APP
; CHECK-NEXT: #NO_APP
; CHECK-NEXT: retl
call i32 asm sideeffect "", "={edi},{edi}"(i32 256)
ret void
}
define void @no_reserve_edi() {
; CHECK-LABEL: no_reserve_edi:
; CHECK: # %bb.0:
; CHECK-NEXT: pushl %edi
; CHECK-NEXT: .cfi_def_cfa_offset 8
; CHECK-NEXT: .cfi_offset %edi, -8
; CHECK-NEXT: movl $256, %edi
; CHECK-NEXT: #APP
; CHECK-NEXT: #NO_APP
; CHECK-NEXT: popl %edi
; CHECK-NEXT: .cfi_def_cfa_offset 4
; CHECK-NEXT: retl
call i32 asm sideeffect "", "={edi},{edi}"(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 @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 @copy_no_reserved(ptr %dst, ptr %src) minsize {
; CHECK-LABEL: copy_no_reserved:
; CHECK: 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_no_reserved(ptr %dst) minsize {
; CHECK-LABEL: set_no_reserved:
; CHECK: rep;stos
call void @llvm.memset.p0.i32(ptr align 4 %dst, i8 0, i32 128, i1 false)
ret void
}