diff --git a/llvm/lib/Target/X86/X86.h b/llvm/lib/Target/X86/X86.h index 6063997094f4..eef4de389a7d 100644 --- a/llvm/lib/Target/X86/X86.h +++ b/llvm/lib/Target/X86/X86.h @@ -27,6 +27,7 @@ namespace llvm { class FunctionPass; class InstructionSelector; +class MachineFunction; class PassRegistry; class X86RegisterBankInfo; class X86Subtarget; @@ -408,6 +409,12 @@ FunctionPass *createX86LowerAMXIntrinsicsLegacyPass(); /// Capacity check and sub-fragment splitting for Win x64 Unwind V3. FunctionPass *createX86WinEHUnwindV3Pass(); +/// Returns true when \p MF must use Windows x64 Unwind V3: the module is in V3 +/// mode, or the function needs an unwind table and may use EGPR (R16-R31), +/// which V1/V2 cannot encode. Shared by frame lowering and the WinEH Unwind +/// V2/V3 passes so their decisions cannot disagree. +bool requireWinX64UnwindV3(const MachineFunction &MF); + InstructionSelector *createX86InstructionSelector(const X86TargetMachine &TM, const X86Subtarget &, const X86RegisterBankInfo &); diff --git a/llvm/lib/Target/X86/X86FrameLowering.cpp b/llvm/lib/Target/X86/X86FrameLowering.cpp index 8ce30b1c4e35..dbe0beb83ac4 100644 --- a/llvm/lib/Target/X86/X86FrameLowering.cpp +++ b/llvm/lib/Target/X86/X86FrameLowering.cpp @@ -12,6 +12,7 @@ #include "X86FrameLowering.h" #include "MCTargetDesc/X86MCTargetDesc.h" +#include "X86.h" #include "X86InstrBuilder.h" #include "X86InstrInfo.h" #include "X86MachineFunctionInfo.h" @@ -46,6 +47,23 @@ STATISTIC(NumFunctionUsingPush2Pop2, "Number of functions using push2/pop2"); using namespace llvm; +bool llvm::requireWinX64UnwindV3(const MachineFunction &MF) { + const Function &Fn = MF.getFunction(); + + // Whole module is in V3 mode. + if (Fn.getParent()->getWinX64EHUnwindMode() == WinX64EHUnwindMode::V3) + return true; + + // Otherwise promote a function that may use EGPR (R16-R31), which V1/V2 + // unwind codes cannot encode. The per-function "+egpr" feature is the signal, + // so an auto-dispatch APX clone gets V3 while the baseline clone stays on the + // module default. We conservatively promote any egpr function rather than + // checking for an actual EGPR save, keeping this a cheap query. (PUSH2/POP2 + // does not need V3: V1/V2 describe a PUSH2 as two SEH_PushReg codes.) + return Fn.needsUnwindTableEntry() && + MF.getSubtarget().hasEGPR(); +} + static const TargetRegisterClass * getCalleeSavedSpillRC(MCRegister Reg, const X86Subtarget &STI, const TargetRegisterInfo &TRI) { @@ -1631,9 +1649,7 @@ void X86FrameLowering::emitPrologue(MachineFunction &MF, MF.getFunction().getParent()->getCodeViewFlag(); bool NeedsWinCFI = NeedsWin64CFI || NeedsWinFPO; bool NeedsDwarfCFI = needsDwarfCFI(MF); - bool IsWin64UnwindV3 = - NeedsWin64CFI && - Fn.getParent()->getWinX64EHUnwindMode() == WinX64EHUnwindMode::V3; + bool IsWin64UnwindV3 = NeedsWin64CFI && requireWinX64UnwindV3(MF); Register FramePtr = TRI->getFrameRegister(MF); const Register MachineFramePtr = STI.isTarget64BitILP32() ? Register(getX86SubSuperRegister(FramePtr, 64)) @@ -2515,9 +2531,7 @@ void X86FrameLowering::emitEpilogue(MachineFunction &MF, // For V3 unwind, epilog SEH pseudos are emitted inline before each // unwind-effecting instruction. bool IsWin64UnwindV3 = - NeedsWin64CFI && MF.hasWinCFI() && - MF.getFunction().getParent()->getWinX64EHUnwindMode() == - WinX64EHUnwindMode::V3; + NeedsWin64CFI && MF.hasWinCFI() && requireWinX64UnwindV3(MF); bool IsFunclet = MBBI == MBB.end() ? false : isFuncletReturnInstr(*MBBI); // Get the number of bytes to allocate from the FrameInfo. @@ -3299,9 +3313,7 @@ bool X86FrameLowering::restoreCalleeSavedRegisters( bool NeedsWin64CFI = isWin64Prologue(MF) && MF.getFunction().needsUnwindTableEntry(); - bool IsWin64UnwindV3 = - NeedsWin64CFI && MF.getFunction().getParent()->getWinX64EHUnwindMode() == - WinX64EHUnwindMode::V3; + bool IsWin64UnwindV3 = NeedsWin64CFI && requireWinX64UnwindV3(MF); // Reload XMMs from stack frame. for (const CalleeSavedInfo &I : CSI) { diff --git a/llvm/lib/Target/X86/X86InstrCompiler.td b/llvm/lib/Target/X86/X86InstrCompiler.td index 5a6597affd51..d3e8fdbdcd53 100644 --- a/llvm/lib/Target/X86/X86InstrCompiler.td +++ b/llvm/lib/Target/X86/X86InstrCompiler.td @@ -260,7 +260,8 @@ let isPseudo = 1, isMeta = 1, isNotDuplicable = 1, SchedRW = [WriteSystem] in { "#SEH_PushFrame $mode", []>; def SEH_EndPrologue : I<0, Pseudo, (outs), (ins), "#SEH_EndPrologue", []>; - def SEH_UnwindVersion : I<0, Pseudo, (outs), (ins i1imm:$version), + // Widened from i1imm to i8imm to hold V3 (the version can be 1, 2 or 3). + def SEH_UnwindVersion : I<0, Pseudo, (outs), (ins i8imm:$version), "#SEH_UnwindVersion $version", []>; } diff --git a/llvm/lib/Target/X86/X86WinEHUnwindV2.cpp b/llvm/lib/Target/X86/X86WinEHUnwindV2.cpp index 254a91174de7..79b91206e9d5 100644 --- a/llvm/lib/Target/X86/X86WinEHUnwindV2.cpp +++ b/llvm/lib/Target/X86/X86WinEHUnwindV2.cpp @@ -383,6 +383,12 @@ bool runX86WinEHUnwindV2(MachineFunction &MF) { Mode != WinX64EHUnwindMode::V2Required) return false; + // A function that requires V3 (see requireWinX64UnwindV3()) is emitted as V3 + // instead; skip it here so this pass does not stamp V2 pseudos that conflict + // with the V3 layout. + if (requireWinX64UnwindV3(MF)) + return false; + // Requested changes. SmallVector FrameInfos; MachineFunction::iterator Iter = MF.begin(); diff --git a/llvm/lib/Target/X86/X86WinEHUnwindV3.cpp b/llvm/lib/Target/X86/X86WinEHUnwindV3.cpp index 162633e2fddf..4efedcd796f2 100644 --- a/llvm/lib/Target/X86/X86WinEHUnwindV3.cpp +++ b/llvm/lib/Target/X86/X86WinEHUnwindV3.cpp @@ -13,7 +13,10 @@ /// 2. Check V3 capacity limits (<=31 prolog/epilog ops, <=7 epilogs). /// 3. Insert sub-fragment split points if limits are exceeded. /// -/// The unwind version is set module-wide, not per-function. +/// The unwind version is normally module-wide. When only an individual function +/// needs V3 (see requireWinX64UnwindV3()), this pass stamps each of its frames +/// -- the entry block and every funclet -- with a per-function +/// .seh_unwindversion 3, leaving the rest of the module on its default version. /// /// See https://learn.microsoft.com/en-us/cpp/build/x64-unwind-information-v3 /// @@ -225,30 +228,21 @@ FuncletInfo X86WinEHUnwindV3::analyzeFunclet(MachineFunction &MF, } bool X86WinEHUnwindV3::runOnMachineFunction(MachineFunction &MF) { - WinX64EHUnwindMode Mode = - MF.getFunction().getParent()->getWinX64EHUnwindMode(); - Function &F = MF.getFunction(); LLVMContext &Ctx = F.getContext(); - // EGPR (R16-R31) requires V3 unwind info because V1/V2 cannot encode - // registers beyond R15. Only enforce this for functions that actually - // emit SEH unwind info — `nounwind` functions and targets that don't - // require unwind tables (e.g. cross-compilation host defaults) can use - // EGPR with any unwind mode since no SEH metadata is generated. - if (Mode != WinX64EHUnwindMode::V3) { - if (!F.needsUnwindTableEntry()) - return false; - const auto &STI = MF.getSubtarget(); - if (STI.hasEGPR()) { - Ctx.diagnose(DiagnosticInfoUnsupported( - F, "EGPR (R16-R31) requires V3 unwind info on Windows x64")); - // Stripping the SEH pseudos modifies the function, so report a change. - suppressWinCFI(MF); - return true; - } + if (!requireWinX64UnwindV3(MF)) return false; - } + + // Emit a per-function .seh_unwindversion 3 only when V3 is enabled for this + // function alone: in module-wide V3 the AsmPrinter emits it once, so stamping + // here would duplicate it. The gate also requires WinCFI -- without a + // .seh_proc there is nothing to version, and a lone SEH pseudo would trip an + // AsmPrinter assertion. The marker is per .seh_proc, hence stamped on each + // funclet in the loop below. + bool PerFunctionV3 = + MF.hasWinCFI() && MF.getFunction().getParent()->getWinX64EHUnwindMode() != + WinX64EHUnwindMode::V3; bool Changed = false; unsigned ApproxBytePos = 0; @@ -259,6 +253,21 @@ bool X86WinEHUnwindV3::runOnMachineFunction(MachineFunction &MF) { // Process each funclet (and the main function body) independently. // Each funclet gets its own UNWIND_INFO, so V3 limits apply per funclet. while (Iter != MF.end()) { + // Iter points at the first block of a frame -- the entry frame on the + // first iteration, an EH funclet on later ones. Each frame is its own + // .seh_proc, so stamp the version on each here before analyzeFunclet + // advances past it. + if (PerFunctionV3) { + const TargetInstrInfo *TII = MF.getSubtarget().getInstrInfo(); + MachineBasicBlock &FuncletEntry = *Iter; + BuildMI(FuncletEntry, FuncletEntry.begin(), + FuncletEntry.findDebugLoc(FuncletEntry.begin()), + TII->get(X86::SEH_UnwindVersion)) + .addImm(3) + .setMIFlag(MachineInstr::FrameSetup); + Changed = true; + } + FuncletInfo Info = analyzeFunclet(MF, Iter, ApproxBytePos); if (Info.PrologOpCount > MaxV3PrologOps) { diff --git a/llvm/test/CodeGen/X86/win64-eh-unwindv3-egpr-funclet.ll b/llvm/test/CodeGen/X86/win64-eh-unwindv3-egpr-funclet.ll new file mode 100644 index 000000000000..dec4b0364db0 --- /dev/null +++ b/llvm/test/CodeGen/X86/win64-eh-unwindv3-egpr-funclet.ll @@ -0,0 +1,31 @@ +; RUN: llc -mtriple=x86_64-unknown-windows-msvc -o - %s | FileCheck %s + +; A per-function V3 promotion stamps every WinEH frame -- the entry frame and +; each EH funclet (each its own .seh_proc) -- with .seh_unwindversion 3. + +; Entry frame is V3. +; CHECK-LABEL: f_eh: +; CHECK: .seh_proc f_eh +; CHECK: .seh_unwindversion 3 +; CHECK: .seh_endproc + +; The cleanup funclet is a separate .seh_proc and must also be V3. +; CHECK: .seh_proc "?dtor${{[^"]*}}" +; CHECK: .seh_unwindversion 3 +; CHECK: .seh_endproc + +define dso_local void @f_eh() #0 personality ptr @__C_specific_handler { +entry: + invoke void @c() to label %ok unwind label %cu +ok: + ret void +cu: + %tok = cleanuppad within none [] + call void @d() [ "funclet"(token %tok) ] + cleanupret from %tok unwind to caller +} + +declare void @c() +declare void @d() +declare dso_local i32 @__C_specific_handler(...) +attributes #0 = { uwtable "target-features"="+egpr" } diff --git a/llvm/test/CodeGen/X86/win64-eh-unwindv3-egpr-no-wincfi.ll b/llvm/test/CodeGen/X86/win64-eh-unwindv3-egpr-no-wincfi.ll new file mode 100644 index 000000000000..722341418e0d --- /dev/null +++ b/llvm/test/CodeGen/X86/win64-eh-unwindv3-egpr-no-wincfi.ll @@ -0,0 +1,16 @@ +; RUN: llc -mtriple=x86_64-unknown-windows-msvc -o - %s | FileCheck %s + +; An egpr leaf with no WinCFI (saves nothing) must NOT get a .seh_proc or +; .seh_unwindversion: the per-function marker is gated on MF.hasWinCFI(). + +; CHECK-LABEL: leaf: +; CHECK-NOT: .seh_proc +; CHECK-NOT: .seh_unwindversion +; CHECK: retq + +define dso_local void @leaf() #0 { +entry: + ret void +} + +attributes #0 = { uwtable "target-features"="+egpr" } diff --git a/llvm/test/CodeGen/X86/win64-eh-unwindv3-egpr-per-function.ll b/llvm/test/CodeGen/X86/win64-eh-unwindv3-egpr-per-function.ll new file mode 100644 index 000000000000..bdc36804e990 --- /dev/null +++ b/llvm/test/CodeGen/X86/win64-eh-unwindv3-egpr-per-function.ll @@ -0,0 +1,32 @@ +; RUN: llc -mtriple=x86_64-unknown-windows-msvc -o - %s | FileCheck %s + +; Models auto-dispatch: in a V1-default module, only the egpr clone +; is promoted to V3; the baseline clone stays on the module default. + +; The baseline function must NOT get a per-function unwind version. +; CHECK-LABEL: baseline: +; CHECK: .seh_proc baseline +; CHECK-NOT: .seh_unwindversion +; CHECK: .seh_endproc + +; The APX clone may use EGPR and is promoted to V3 individually. +; CHECK-LABEL: apx_clone: +; CHECK: .seh_proc apx_clone +; CHECK: .seh_unwindversion 3 +; CHECK: .seh_endproc + +define dso_local void @baseline() #1 { +entry: + call void @ext() + ret void +} + +define dso_local void @apx_clone() #0 { +entry: + call void @ext() + ret void +} + +declare void @ext() +attributes #0 = { uwtable "target-features"="+egpr" } +attributes #1 = { uwtable } diff --git a/llvm/test/CodeGen/X86/win64-eh-unwindv3-egpr-required.ll b/llvm/test/CodeGen/X86/win64-eh-unwindv3-egpr-required.ll deleted file mode 100644 index f33893a9e1d8..000000000000 --- a/llvm/test/CodeGen/X86/win64-eh-unwindv3-egpr-required.ll +++ /dev/null @@ -1,16 +0,0 @@ -; RUN: not llc -mtriple=x86_64-unknown-windows-msvc -mattr=+egpr -o /dev/null %s 2>&1 | FileCheck %s -; CHECK: error: {{.*}}: in function func {{.*}}: EGPR (R16-R31) requires V3 unwind info on Windows x64 - -; EGPR enabled without V3 unwind (default V1) should produce a recoverable -; backend diagnostic (no crash, no stack trace). -; The uwtable attribute and the call site force a stack frame and SEH unwind -; info emission so the V3 pass runs regardless of the host platform's default -; (matters for cross-compilation on Linux/macOS hosts targeting Windows). - -declare void @other() - -define dso_local void @func() uwtable { -entry: - call void @other() - ret void -} diff --git a/llvm/test/CodeGen/X86/win64-eh-unwindv3-egpr-v2-module.ll b/llvm/test/CodeGen/X86/win64-eh-unwindv3-egpr-v2-module.ll new file mode 100644 index 000000000000..f914b20b0e29 --- /dev/null +++ b/llvm/test/CodeGen/X86/win64-eh-unwindv3-egpr-v2-module.ll @@ -0,0 +1,39 @@ +; RUN: llc -mtriple=x86_64-unknown-windows-msvc -o - %s | FileCheck %s + +; In a V2-default module, the egpr function is promoted to V3 (with no V2 +; markers) while the non-egpr function stays on V2. + +; The non-EGPR function stays on V2 with its V2 epilog markers. +; CHECK-LABEL: v2_baseline: +; CHECK: .seh_proc v2_baseline +; CHECK: .seh_unwindversion 2 +; CHECK-NOT: .seh_unwindversion 3 +; CHECK: .seh_unwindv2start +; CHECK: .seh_endproc + +; The EGPR function is emitted as V3 with no V2 markers. +; CHECK-LABEL: v2_egpr: +; CHECK: .seh_proc v2_egpr +; CHECK: .seh_unwindversion 3 +; CHECK-NOT: .seh_unwindversion 2 +; CHECK-NOT: .seh_unwindv2start +; CHECK: .seh_endproc + +define dso_local void @v2_baseline() #1 { +entry: + call void @ext() + ret void +} + +define dso_local void @v2_egpr() #0 { +entry: + call void @ext() + ret void +} + +declare void @ext() +attributes #0 = { uwtable "target-features"="+egpr" } +attributes #1 = { uwtable } + +!llvm.module.flags = !{!0} +!0 = !{i32 1, !"winx64-eh-unwind", i32 2} diff --git a/llvm/test/CodeGen/X86/win64-eh-unwindv3-egpr-version-switch-v1.ll b/llvm/test/CodeGen/X86/win64-eh-unwindv3-egpr-version-switch-v1.ll new file mode 100644 index 000000000000..efd707e8072e --- /dev/null +++ b/llvm/test/CodeGen/X86/win64-eh-unwindv3-egpr-version-switch-v1.ll @@ -0,0 +1,53 @@ +; RUN: llc -mtriple=x86_64-unknown-windows-msvc -o - %s | FileCheck %s + +; Like win64-eh-unwindv3-egpr-version-switch.ll but in a V1-default +; module: each egpr function toggles to V3, each non-egpr function stays on V1 +; (no marker). + +; CHECK-LABEL: a_egpr: +; CHECK: .seh_proc a_egpr +; CHECK: .seh_unwindversion 3 +; CHECK: .seh_endproc + +; CHECK-LABEL: b_base: +; CHECK: .seh_proc b_base +; CHECK-NOT: .seh_unwindversion +; CHECK: .seh_endproc + +; CHECK-LABEL: c_egpr: +; CHECK: .seh_proc c_egpr +; CHECK: .seh_unwindversion 3 +; CHECK: .seh_endproc + +; CHECK-LABEL: d_base: +; CHECK: .seh_proc d_base +; CHECK-NOT: .seh_unwindversion +; CHECK: .seh_endproc + +define dso_local void @a_egpr() #0 { +entry: + call void @ext() + ret void +} + +define dso_local void @b_base() #1 { +entry: + call void @ext() + ret void +} + +define dso_local void @c_egpr() #0 { +entry: + call void @ext() + ret void +} + +define dso_local void @d_base() #1 { +entry: + call void @ext() + ret void +} + +declare void @ext() +attributes #0 = { uwtable "target-features"="+egpr" } +attributes #1 = { uwtable } diff --git a/llvm/test/CodeGen/X86/win64-eh-unwindv3-egpr-version-switch.ll b/llvm/test/CodeGen/X86/win64-eh-unwindv3-egpr-version-switch.ll new file mode 100644 index 000000000000..6136fb64516e --- /dev/null +++ b/llvm/test/CodeGen/X86/win64-eh-unwindv3-egpr-version-switch.ll @@ -0,0 +1,47 @@ +; RUN: llc -mtriple=x86_64-unknown-windows-msvc -o - %s | FileCheck %s + +; Interleaved egpr / non-egpr functions in a V2 module: the per-function +; version must alternate both ways, 3, 2, 3, 2. + +; CHECK-LABEL: a_egpr: +; CHECK: .seh_unwindversion 3 +; CHECK-NOT: .seh_unwindversion +; CHECK-LABEL: b_base: +; CHECK: .seh_unwindversion 2 +; CHECK-NOT: .seh_unwindversion +; CHECK-LABEL: c_egpr: +; CHECK: .seh_unwindversion 3 +; CHECK-NOT: .seh_unwindversion +; CHECK-LABEL: d_base: +; CHECK: .seh_unwindversion 2 + +define dso_local void @a_egpr() #0 { +entry: + call void @ext() + ret void +} + +define dso_local void @b_base() #1 { +entry: + call void @ext() + ret void +} + +define dso_local void @c_egpr() #0 { +entry: + call void @ext() + ret void +} + +define dso_local void @d_base() #1 { +entry: + call void @ext() + ret void +} + +declare void @ext() +attributes #0 = { uwtable "target-features"="+egpr" } +attributes #1 = { uwtable } + +!llvm.module.flags = !{!0} +!0 = !{i32 1, !"winx64-eh-unwind", i32 2}