Skip to content

Commit 72c3ed6

Browse files
authored
[win][x64] Unwind v2 3/n: Add support for emitting unwind v2 information (equivalent to MSVC /d2epilogunwind) (#129142)
Adds support for emitting Windows x64 Unwind V2 information, includes support `/d2epilogunwind` in clang-cl. Unwind v2 adds information about the epilogs in functions such that the unwinder can unwind even in the middle of an epilog, without having to disassembly the function to see what has or has not been cleaned up. Unwind v2 requires that all epilogs are in "canonical" form: * If there was a stack allocation (fixed or dynamic) in the prolog, then the first instruction in the epilog must be a stack deallocation. * Next, for each `PUSH` in the prolog there must be a corresponding `POP` instruction in exact reverse order. * Finally, the epilog must end with the terminator. This change adds a pass to validate epilogs in modules that have Unwind v2 enabled and, if they pass, emits new pseudo instructions to MC that 1) note that the function is using unwind v2 and 2) mark the start of the epilog (this is either the first `POP` if there is one, otherwise the terminator instruction). If a function does not meet these requirements, it is downgraded to Unwind v1 (i.e., these new pseudo instructions are not emitted). Note that the unwind v2 table only marks the size of the epilog in the "header" unwind code, but it's possible for epilogs to use different terminator instructions thus they are not all the same size. As a work around for this, MC will assume that all terminator instructions are 1-byte long - this still works correctly with the Windows unwinder as it is only using the size to do a range check to see if a thread is in an epilog or not, and since the instruction pointer will never be in the middle of an instruction and the terminator is always at the end of an epilog the range check will function correctly. This does mean, however, that the "at end" optimization (where an epilog unwind code can be elided if the last epilog is at the end of the function) can only be used if the terminator is 1-byte long. One other complication with the implementation is that the unwind table for a function is emitted during streaming, however we can't calculate the distance between an epilog and the end of the function at that time as layout hasn't been completed yet (thus some instructions may be relaxed). To work around this, epilog unwind codes are emitted via a fixup. This also means that we can't pre-emptively downgrade a function to Unwind v1 if one of these offsets is too large, so instead we raise an error (but I've passed through the location information, so the user will know which of their functions is problematic).
1 parent 94ae5f9 commit 72c3ed6

24 files changed

+923
-33
lines changed

clang/include/clang/Basic/CodeGenOptions.def

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -483,6 +483,9 @@ CODEGENOPT(StaticClosure, 1, 0)
483483
/// Assume that UAVs/SRVs may alias
484484
CODEGENOPT(ResMayAlias, 1, 0)
485485

486+
/// Enables unwind v2 (epilog) information for x64 Windows.
487+
CODEGENOPT(WinX64EHUnwindV2, 1, 0)
488+
486489
/// FIXME: Make DebugOptions its own top-level .def file.
487490
#include "DebugOptions.def"
488491

clang/include/clang/Driver/Options.td

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2167,6 +2167,11 @@ defm assume_nothrow_exception_dtor: BoolFOption<"assume-nothrow-exception-dtor",
21672167
LangOpts<"AssumeNothrowExceptionDtor">, DefaultFalse,
21682168
PosFlag<SetTrue, [], [ClangOption, CC1Option], "Assume that exception objects' destructors are non-throwing">,
21692169
NegFlag<SetFalse>>;
2170+
defm winx64_eh_unwindv2 : BoolFOption<"winx64-eh-unwindv2",
2171+
CodeGenOpts<"WinX64EHUnwindV2">, DefaultFalse,
2172+
PosFlag<SetTrue, [], [ClangOption, CC1Option], "Enable">,
2173+
NegFlag<SetFalse, [], [ClangOption], "Disable">,
2174+
BothFlags<[], [ClangOption], " unwind v2 (epilog) information for x64 Windows">>;
21702175
def fexcess_precision_EQ : Joined<["-"], "fexcess-precision=">, Group<f_Group>,
21712176
Visibility<[ClangOption, CLOption]>,
21722177
HelpText<"Allows control over excess precision on targets where native "
@@ -8947,6 +8952,8 @@ def _SLASH_M_Group : OptionGroup<"</M group>">, Group<cl_compile_Group>;
89478952
def _SLASH_volatile_Group : OptionGroup<"</volatile group>">,
89488953
Group<cl_compile_Group>;
89498954

8955+
def _SLASH_d2epilogunwind : CLFlag<"d2epilogunwind">,
8956+
HelpText<"Enable unwind v2 (epilog) information for x64 Windows">;
89508957
def _SLASH_EH : CLJoined<"EH">, HelpText<"Set exception handling model">;
89518958
def _SLASH_EP : CLFlag<"EP">,
89528959
HelpText<"Disable linemarker output and preprocess to stdout">;

clang/lib/CodeGen/CodeGenModule.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1307,6 +1307,10 @@ void CodeGenModule::Release() {
13071307
getModule().addModuleFlag(llvm::Module::Warning, "import-call-optimization",
13081308
1);
13091309

1310+
// Enable unwind v2 (epilog).
1311+
if (CodeGenOpts.WinX64EHUnwindV2)
1312+
getModule().addModuleFlag(llvm::Module::Warning, "winx64-eh-unwindv2", 1);
1313+
13101314
// Indicate whether this Module was compiled with -fopenmp
13111315
if (getLangOpts().OpenMP && !getLangOpts().OpenMPSimd)
13121316
getModule().addModuleFlag(llvm::Module::Max, "openmp", LangOpts.OpenMP);

clang/lib/Driver/ToolChains/Clang.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7504,6 +7504,10 @@ void Clang::ConstructJob(Compilation &C, const JobAction &JA,
75047504
}
75057505
}
75067506

7507+
// Unwind v2 (epilog) information for x64 Windows.
7508+
Args.addOptInFlag(CmdArgs, options::OPT_fwinx64_eh_unwindv2,
7509+
options::OPT_fno_winx64_eh_unwindv2);
7510+
75077511
// C++ "sane" operator new.
75087512
Args.addOptOutFlag(CmdArgs, options::OPT_fassume_sane_operator_new,
75097513
options::OPT_fno_assume_sane_operator_new);
@@ -8549,6 +8553,10 @@ void Clang::AddClangCLArgs(const ArgList &Args, types::ID InputType,
85498553
if (Args.hasArg(options::OPT__SLASH_kernel))
85508554
CmdArgs.push_back("-fms-kernel");
85518555

8556+
// Unwind v2 (epilog) information for x64 Windows.
8557+
if (Args.hasArg(options::OPT__SLASH_d2epilogunwind))
8558+
CmdArgs.push_back("-fwinx64-eh-unwindv2");
8559+
85528560
for (const Arg *A : Args.filtered(options::OPT__SLASH_guard)) {
85538561
StringRef GuardArgs = A->getValue();
85548562
// The only valid options are "cf", "cf,nochecks", "cf-", "ehcont" and

clang/test/CodeGen/epilog-unwind.c

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// RUN: %clang_cc1 -emit-llvm %s -o - | FileCheck %s -check-prefix=DISABLED
2+
// RUN: %clang_cc1 -fwinx64-eh-unwindv2 -emit-llvm %s -o - | FileCheck %s -check-prefix=ENABLED
3+
// RUN: %clang -fwinx64-eh-unwindv2 -S -emit-llvm %s -o - | FileCheck %s -check-prefix=ENABLED
4+
// RUN: %clang -fno-winx64-eh-unwindv2 -S -emit-llvm %s -o - | FileCheck %s -check-prefix=DISABLED
5+
6+
void f(void) {}
7+
8+
// ENABLED: !"winx64-eh-unwindv2", i32 1}
9+
// DISABLED-NOT: "winx64-eh-unwindv2"

clang/test/Driver/cl-options.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -820,4 +820,7 @@
820820
// RUN: %clang_cl -vctoolsdir "" /arm64EC /c -target x86_64-pc-windows-msvc -### -- %s 2>&1 | FileCheck --check-prefix=ARM64EC_OVERRIDE %s
821821
// ARM64EC_OVERRIDE: warning: /arm64EC has been overridden by specified target: x86_64-pc-windows-msvc; option ignored
822822

823+
// RUN: %clang_cl /d2epilogunwind /c -### -- %s 2>&1 | FileCheck %s --check-prefix=EPILOGUNWIND
824+
// EPILOGUNWIND: -fwinx64-eh-unwindv2
825+
823826
void f(void) { }

llvm/include/llvm/MC/MCStreamer.h

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -255,11 +255,8 @@ class MCStreamer {
255255
bool AllowAutoPadding = false;
256256

257257
protected:
258-
// True if we are processing SEH directives in an epilogue.
259-
bool InEpilogCFI = false;
260-
261258
// Symbol of the current epilog for which we are processing SEH directives.
262-
MCSymbol *CurrentEpilog = nullptr;
259+
WinEH::FrameInfo::Epilog *CurrentWinEpilog = nullptr;
263260

264261
MCFragment *CurFrag = nullptr;
265262

@@ -342,9 +339,11 @@ class MCStreamer {
342339
return WinFrameInfos;
343340
}
344341

345-
MCSymbol *getCurrentEpilog() const { return CurrentEpilog; }
342+
WinEH::FrameInfo::Epilog *getCurrentWinEpilog() const {
343+
return CurrentWinEpilog;
344+
}
346345

347-
bool isInEpilogCFI() const { return InEpilogCFI; }
346+
bool isInEpilogCFI() const { return CurrentWinEpilog; }
348347

349348
void generateCompactUnwindEncodings(MCAsmBackend *MAB);
350349

@@ -1026,6 +1025,8 @@ class MCStreamer {
10261025
virtual void emitWinCFIEndProlog(SMLoc Loc = SMLoc());
10271026
virtual void emitWinCFIBeginEpilogue(SMLoc Loc = SMLoc());
10281027
virtual void emitWinCFIEndEpilogue(SMLoc Loc = SMLoc());
1028+
virtual void emitWinCFIUnwindV2Start(SMLoc Loc = SMLoc());
1029+
virtual void emitWinCFIUnwindVersion(uint8_t Version, SMLoc Loc = SMLoc());
10291030
virtual void emitWinEHHandler(const MCSymbol *Sym, bool Unwind, bool Except,
10301031
SMLoc Loc = SMLoc());
10311032
virtual void emitWinEHHandlerData(SMLoc Loc = SMLoc());

llvm/include/llvm/MC/MCWinEH.h

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#define LLVM_MC_MCWINEH_H
1111

1212
#include "llvm/ADT/MapVector.h"
13+
#include "llvm/Support/SMLoc.h"
1314
#include <vector>
1415

1516
namespace llvm {
@@ -42,6 +43,7 @@ struct FrameInfo {
4243
const MCSymbol *FuncletOrFuncEnd = nullptr;
4344
const MCSymbol *ExceptionHandler = nullptr;
4445
const MCSymbol *Function = nullptr;
46+
SMLoc FunctionLoc;
4547
const MCSymbol *PrologEnd = nullptr;
4648
const MCSymbol *Symbol = nullptr;
4749
MCSection *TextSection = nullptr;
@@ -52,14 +54,19 @@ struct FrameInfo {
5254
bool HandlesExceptions = false;
5355
bool EmitAttempted = false;
5456
bool Fragment = false;
57+
constexpr static uint8_t DefaultVersion = 1;
58+
uint8_t Version = DefaultVersion;
5559

5660
int LastFrameInst = -1;
5761
const FrameInfo *ChainedParent = nullptr;
5862
std::vector<Instruction> Instructions;
5963
struct Epilog {
6064
std::vector<Instruction> Instructions;
6165
unsigned Condition;
62-
MCSymbol *End;
66+
const MCSymbol *Start = nullptr;
67+
const MCSymbol *End = nullptr;
68+
const MCSymbol *UnwindV2Start = nullptr;
69+
SMLoc Loc;
6370
};
6471
MapVector<MCSymbol *, Epilog> EpilogMap;
6572

llvm/lib/MC/MCAsmStreamer.cpp

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,8 @@ class MCAsmStreamer final : public MCStreamer {
391391
void emitWinCFIEndProlog(SMLoc Loc) override;
392392
void emitWinCFIBeginEpilogue(SMLoc Loc) override;
393393
void emitWinCFIEndEpilogue(SMLoc Loc) override;
394+
void emitWinCFIUnwindV2Start(SMLoc Loc) override;
395+
void emitWinCFIUnwindVersion(uint8_t Version, SMLoc Loc) override;
394396

395397
void emitWinEHHandler(const MCSymbol *Sym, bool Unwind, bool Except,
396398
SMLoc Loc) override;
@@ -2305,6 +2307,20 @@ void MCAsmStreamer::emitWinCFIEndEpilogue(SMLoc Loc) {
23052307
EmitEOL();
23062308
}
23072309

2310+
void MCAsmStreamer::emitWinCFIUnwindV2Start(SMLoc Loc) {
2311+
MCStreamer::emitWinCFIUnwindV2Start(Loc);
2312+
2313+
OS << "\t.seh_unwindv2start";
2314+
EmitEOL();
2315+
}
2316+
2317+
void MCAsmStreamer::emitWinCFIUnwindVersion(uint8_t Version, SMLoc Loc) {
2318+
MCStreamer::emitWinCFIUnwindVersion(Version, Loc);
2319+
2320+
OS << "\t.seh_unwindversion " << (unsigned)Version;
2321+
EmitEOL();
2322+
}
2323+
23082324
void MCAsmStreamer::emitCGProfileEntry(const MCSymbolRefExpr *From,
23092325
const MCSymbolRefExpr *To,
23102326
uint64_t Count) {

llvm/lib/MC/MCParser/COFFAsmParser.cpp

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,10 @@ class COFFAsmParser : public MCAsmParserExtension {
9696
".seh_startepilogue");
9797
addDirectiveHandler<&COFFAsmParser::ParseSEHDirectiveEndEpilog>(
9898
".seh_endepilogue");
99+
addDirectiveHandler<&COFFAsmParser::ParseSEHDirectiveUnwindV2Start>(
100+
".seh_unwindv2start");
101+
addDirectiveHandler<&COFFAsmParser::ParseSEHDirectiveUnwindVersion>(
102+
".seh_unwindversion");
99103
}
100104

101105
bool parseSectionDirectiveText(StringRef, SMLoc) {
@@ -147,6 +151,8 @@ class COFFAsmParser : public MCAsmParserExtension {
147151
bool parseSEHDirectiveEndProlog(StringRef, SMLoc);
148152
bool ParseSEHDirectiveBeginEpilog(StringRef, SMLoc);
149153
bool ParseSEHDirectiveEndEpilog(StringRef, SMLoc);
154+
bool ParseSEHDirectiveUnwindV2Start(StringRef, SMLoc);
155+
bool ParseSEHDirectiveUnwindVersion(StringRef, SMLoc);
150156

151157
bool parseAtUnwindOrAtExcept(bool &unwind, bool &except);
152158
bool parseDirectiveSymbolAttribute(StringRef Directive, SMLoc);
@@ -774,6 +780,28 @@ bool COFFAsmParser::ParseSEHDirectiveEndEpilog(StringRef, SMLoc Loc) {
774780
return false;
775781
}
776782

783+
bool COFFAsmParser::ParseSEHDirectiveUnwindV2Start(StringRef, SMLoc Loc) {
784+
Lex();
785+
getStreamer().emitWinCFIUnwindV2Start(Loc);
786+
return false;
787+
}
788+
789+
bool COFFAsmParser::ParseSEHDirectiveUnwindVersion(StringRef, SMLoc Loc) {
790+
int64_t Version;
791+
if (getParser().parseIntToken(Version, "expected unwind version number"))
792+
return true;
793+
794+
if ((Version < 1) || (Version > UINT8_MAX))
795+
return Error(Loc, "invalid unwind version");
796+
797+
if (getLexer().isNot(AsmToken::EndOfStatement))
798+
return TokError("unexpected token in directive");
799+
800+
Lex();
801+
getStreamer().emitWinCFIUnwindVersion(Version, Loc);
802+
return false;
803+
}
804+
777805
bool COFFAsmParser::parseAtUnwindOrAtExcept(bool &unwind, bool &except) {
778806
StringRef identifier;
779807
if (getLexer().isNot(AsmToken::At) && getLexer().isNot(AsmToken::Percent))

llvm/lib/MC/MCStreamer.cpp

Lines changed: 47 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -743,6 +743,7 @@ void MCStreamer::emitWinCFIStartProc(const MCSymbol *Symbol, SMLoc Loc) {
743743
std::make_unique<WinEH::FrameInfo>(Symbol, StartProc));
744744
CurrentWinFrameInfo = WinFrameInfos.back().get();
745745
CurrentWinFrameInfo->TextSection = getCurrentSectionOnly();
746+
CurrentWinFrameInfo->FunctionLoc = Loc;
746747
}
747748

748749
void MCStreamer::emitWinCFIEndProc(SMLoc Loc) {
@@ -1000,23 +1001,63 @@ void MCStreamer::emitWinCFIBeginEpilogue(SMLoc Loc) {
10001001
"(.seh_endprologue) in " +
10011002
CurFrame->Function->getName());
10021003

1003-
InEpilogCFI = true;
1004-
CurrentEpilog = emitCFILabel();
1004+
MCSymbol *Label = emitCFILabel();
1005+
CurrentWinEpilog =
1006+
&CurFrame->EpilogMap.insert_or_assign(Label, WinEH::FrameInfo::Epilog())
1007+
.first->second;
1008+
CurrentWinEpilog->Start = Label;
1009+
CurrentWinEpilog->Loc = Loc;
10051010
}
10061011

10071012
void MCStreamer::emitWinCFIEndEpilogue(SMLoc Loc) {
10081013
WinEH::FrameInfo *CurFrame = EnsureValidWinFrameInfo(Loc);
10091014
if (!CurFrame)
10101015
return;
10111016

1012-
if (!InEpilogCFI)
1017+
if (!CurrentWinEpilog)
10131018
return getContext().reportError(Loc, "Stray .seh_endepilogue in " +
10141019
CurFrame->Function->getName());
10151020

1016-
InEpilogCFI = false;
1021+
if ((CurFrame->Version >= 2) && !CurrentWinEpilog->UnwindV2Start)
1022+
return getContext().reportError(Loc, "Missing .seh_unwindv2start in " +
1023+
CurFrame->Function->getName());
1024+
1025+
CurrentWinEpilog->End = emitCFILabel();
1026+
CurrentWinEpilog = nullptr;
1027+
}
1028+
1029+
void MCStreamer::emitWinCFIUnwindV2Start(SMLoc Loc) {
1030+
WinEH::FrameInfo *CurFrame = EnsureValidWinFrameInfo(Loc);
1031+
if (!CurFrame)
1032+
return;
1033+
1034+
if (!CurrentWinEpilog)
1035+
return getContext().reportError(Loc, "Stray .seh_unwindv2start in " +
1036+
CurFrame->Function->getName());
1037+
1038+
if (CurrentWinEpilog->UnwindV2Start)
1039+
return getContext().reportError(Loc, "Duplicate .seh_unwindv2start in " +
1040+
CurFrame->Function->getName());
1041+
10171042
MCSymbol *Label = emitCFILabel();
1018-
CurFrame->EpilogMap[CurrentEpilog].End = Label;
1019-
CurrentEpilog = nullptr;
1043+
CurrentWinEpilog->UnwindV2Start = Label;
1044+
}
1045+
1046+
void MCStreamer::emitWinCFIUnwindVersion(uint8_t Version, SMLoc Loc) {
1047+
WinEH::FrameInfo *CurFrame = EnsureValidWinFrameInfo(Loc);
1048+
if (!CurFrame)
1049+
return;
1050+
1051+
if (CurFrame->Version != WinEH::FrameInfo::DefaultVersion)
1052+
return getContext().reportError(Loc, "Duplicate .seh_unwindversion in " +
1053+
CurFrame->Function->getName());
1054+
1055+
if (Version != 2)
1056+
return getContext().reportError(
1057+
Loc, "Unsupported version specified in .seh_unwindversion in " +
1058+
CurFrame->Function->getName());
1059+
1060+
CurFrame->Version = Version;
10201061
}
10211062

10221063
void MCStreamer::emitCOFFSafeSEH(MCSymbol const *Symbol) {}

0 commit comments

Comments
 (0)