[llvm] [llvm-ml] Add MASM unwind v3 support for x64 exception handling and improve MSVC compat (PR #202809)
Daniel Paoliello via llvm-commits
llvm-commits at lists.llvm.org
Thu Jun 11 09:17:26 PDT 2026
https://github.com/dpaoliello updated https://github.com/llvm/llvm-project/pull/202809
>From b8e704fa8148665d356b37cad09c5efb04c4da5a Mon Sep 17 00:00:00 2001
From: "Daniel Paoliello (HE HIM)" <danpao at microsoft.com>
Date: Wed, 25 Mar 2026 19:08:32 +0000
Subject: [PATCH 1/3] Merged PR 15128428: [llvm-ml] Add MASM unwind v3 support
for x64 exception handling and improve MSVC compat
New command-line options:
- `/unwindv3`: Enable V3 unwind information format
- `--mattr`: Enable target-specific CPU features (e.g. `--mattr=+push2pop2`)
New MASM directives:
- `.push2reg` / `.pop2reg`: Push/pop register pairs (PUSH2/POP2)
- `.beginepilog` / `.endepilog`: Delimit epilog unwind regions
- `.popreg`, `.freestack`, `.restorereg`, `.restorexmm128`, `.unsetframe`:
Epilog counterparts of existing prolog directives
- `.pushframe code`: MASM syntax for interrupt handlers with error codes
New built-in symbol:
- `@UnwindVersion`: Returns the current x64 unwind version being used.
Error diagnostics:
- Prolog directives after `.endprolog` are diagnosed
- Epilog directives outside `.beginepilog`/`.endepilog` are diagnosed
- Nested `.beginepilog` is diagnosed
---
llvm/docs/ReleaseNotes.md | 20 ++
llvm/include/llvm/MC/MCStreamer.h | 10 +
llvm/lib/MC/MCParser/COFFMasmParser.cpp | 83 ++++++
llvm/lib/MC/MCParser/MasmParser.cpp | 12 +
.../lib/Target/X86/AsmParser/X86AsmParser.cpp | 86 +++++-
llvm/test/tools/llvm-ml/beginepilog_error.asm | 19 ++
llvm/test/tools/llvm-ml/builtin_symbols.asm | 6 +
.../tools/llvm-ml/epilog_directive_errors.asm | 42 +++
llvm/test/tools/llvm-ml/epilog_directives.asm | 278 ++++++++++++++++++
llvm/test/tools/llvm-ml/proc_frame_v3.asm | 177 +++++++++++
.../tools/llvm-ml/prolog_directive_errors.asm | 45 +++
llvm/tools/llvm-ml/Opts.td | 5 +
llvm/tools/llvm-ml/llvm-ml.cpp | 25 +-
13 files changed, 794 insertions(+), 14 deletions(-)
create mode 100644 llvm/test/tools/llvm-ml/beginepilog_error.asm
create mode 100644 llvm/test/tools/llvm-ml/epilog_directive_errors.asm
create mode 100644 llvm/test/tools/llvm-ml/epilog_directives.asm
create mode 100644 llvm/test/tools/llvm-ml/proc_frame_v3.asm
create mode 100644 llvm/test/tools/llvm-ml/prolog_directive_errors.asm
diff --git a/llvm/docs/ReleaseNotes.md b/llvm/docs/ReleaseNotes.md
index bdfbfc3f70533..ba042577c84ac 100644
--- a/llvm/docs/ReleaseNotes.md
+++ b/llvm/docs/ReleaseNotes.md
@@ -321,6 +321,26 @@ Makes programs 10x faster by doing Special New Thing.
### Changes to the LLVM tools
+* llvm-ml now supports the `--mattr` flag for enabling target-specific CPU
+ features (e.g. `--mattr=+push2pop2`).
+* llvm-ml now supports the `/unwindv3` flag to enable V3 unwind information
+ format for x64 exception handling.
+* llvm-ml now supports the `@UnwindVersion` built-in symbol, which returns the
+ current unwind version (1 by default, 3 when `/unwindv3` is specified).
+* llvm-ml now supports the `.push2reg`, `.pop2reg`, `.beginepilog`, and
+ `.endepilog` MASM directives for V3 unwind information.
+* llvm-ml now supports the `.popreg`, `.freestack`, `.restorereg`,
+ `.restorexmm128`, and `.unsetframe` MASM epilog directives. These are the
+ epilog counterparts of `.pushreg`, `.allocstack`, `.savereg`,
+ `.savexmm128`, and `.setframe` respectively, and are valid only inside
+ `.beginepilog`/`.endepilog` blocks.
+* llvm-ml now supports `.pushframe code` syntax (without the `@` prefix)
+ for interrupt handlers with error codes.
+* llvm-ml now diagnoses:
+ - Prolog directives (including `.allocstack`) used outside of prologs (after `.endprolog`).
+ - Epilog directives used outside of epilogs (outside of `.beginepilog` + `.endepilog` blocks).
+ - Beginning an epilog (`.beginepilog`) inside another epilog.
+
* `llvm-profgen` now supports ETM trace decoding using the OpenCSD library for Cortex-M targets. OpenCSD version 1.5.4 or higher is required.
* `llvm-objcopy` no longer corrupts the symbol table when `--update-section` is called for ELF files.
diff --git a/llvm/include/llvm/MC/MCStreamer.h b/llvm/include/llvm/MC/MCStreamer.h
index 750e3269cc0e3..955a48705f15c 100644
--- a/llvm/include/llvm/MC/MCStreamer.h
+++ b/llvm/include/llvm/MC/MCStreamer.h
@@ -366,6 +366,13 @@ class LLVM_ABI MCStreamer {
bool isInEpilogCFI() const { return CurrentWinEpilog; }
+ /// Returns true if a WinCFI prolog has been completed (.seh_endprologue)
+ /// in the current frame.
+ bool isWinCFIPrologEnded() const {
+ return CurrentWinFrameInfo && !CurrentWinFrameInfo->End &&
+ CurrentWinFrameInfo->PrologEnd;
+ }
+
/// \name Assembly File Formatting.
/// @{
@@ -1088,6 +1095,9 @@ class LLVM_ABI MCStreamer {
void setDefaultWinCFIUnwindVersion(uint8_t V) {
DefaultWinCFIUnwindVersion = V;
}
+ uint8_t getDefaultWinCFIUnwindVersion() const {
+ return DefaultWinCFIUnwindVersion;
+ }
virtual void emitWinEHHandler(const MCSymbol *Sym, bool Unwind, bool Except,
SMLoc Loc = SMLoc());
virtual void emitWinEHHandlerData(SMLoc Loc = SMLoc());
diff --git a/llvm/lib/MC/MCParser/COFFMasmParser.cpp b/llvm/lib/MC/MCParser/COFFMasmParser.cpp
index db796a3ad2ed8..7ba3aa0256190 100644
--- a/llvm/lib/MC/MCParser/COFFMasmParser.cpp
+++ b/llvm/lib/MC/MCParser/COFFMasmParser.cpp
@@ -49,7 +49,17 @@ class COFFMasmParser : public MCAsmParserExtension {
bool parseDirectiveAlias(StringRef, SMLoc);
bool parseSEHDirectiveAllocStack(StringRef, SMLoc);
+ bool parseSEHDirectiveFreeStack(StringRef, SMLoc);
bool parseSEHDirectiveEndProlog(StringRef, SMLoc);
+ bool ParseSEHDirectiveBeginEpilog(StringRef, SMLoc);
+ bool ParseSEHDirectiveEndEpilog(StringRef, SMLoc);
+
+ /// Check that we are inside a PROC FRAME.
+ bool ensureInsideFrame(SMLoc Loc, StringRef Directive);
+ /// Check that we are in the prolog (before .endprolog).
+ bool ensureInProlog(SMLoc Loc, StringRef Directive);
+ /// Check that we are inside a .beginepilog/.endepilog block.
+ bool ensureInEpilog(SMLoc Loc, StringRef Directive);
bool IgnoreDirective(StringRef, SMLoc) {
while (!getLexer().is(AsmToken::EndOfStatement)) {
@@ -65,8 +75,14 @@ class COFFMasmParser : public MCAsmParserExtension {
// x64 directives
addDirectiveHandler<&COFFMasmParser::parseSEHDirectiveAllocStack>(
".allocstack");
+ addDirectiveHandler<&COFFMasmParser::parseSEHDirectiveFreeStack>(
+ ".freestack");
addDirectiveHandler<&COFFMasmParser::parseSEHDirectiveEndProlog>(
".endprolog");
+ addDirectiveHandler<&COFFMasmParser::ParseSEHDirectiveBeginEpilog>(
+ ".beginepilog");
+ addDirectiveHandler<&COFFMasmParser::ParseSEHDirectiveEndEpilog>(
+ ".endepilog");
// Code label directives
// label
@@ -517,12 +533,58 @@ bool COFFMasmParser::parseDirectiveAlias(StringRef Directive, SMLoc Loc) {
return false;
}
+bool COFFMasmParser::ensureInsideFrame(SMLoc Loc, StringRef Directive) {
+ if (CurrentProceduresFramed.empty() || !CurrentProceduresFramed.back()) {
+ return Error(Loc,
+ "Missing Frame in proc, no unwind code will be generated.");
+ }
+ return false;
+}
+
+bool COFFMasmParser::ensureInProlog(SMLoc Loc, StringRef Directive) {
+ if (ensureInsideFrame(Loc, Directive))
+ return true;
+ if (getStreamer().isWinCFIPrologEnded()) {
+ return Error(Loc, "prolog directive must be used inside a prolog");
+ }
+ return false;
+}
+
+bool COFFMasmParser::ensureInEpilog(SMLoc Loc, StringRef Directive) {
+ if (ensureInsideFrame(Loc, Directive))
+ return true;
+ if (!getStreamer().isInEpilogCFI()) {
+ return Error(Loc, "epilog directive must be used inside an epilog");
+ }
+ return false;
+}
+
bool COFFMasmParser::parseSEHDirectiveAllocStack(StringRef Directive,
SMLoc Loc) {
+ if (ensureInProlog(Loc, Directive))
+ return true;
+ int64_t Size;
+ SMLoc SizeLoc = getTok().getLoc();
+ if (getParser().parseAbsoluteExpression(Size))
+ return Error(SizeLoc, "expected integer size");
+ if (Size < 0)
+ return Error(SizeLoc, "stack size must be non-negative");
+ if (Size % 8 != 0)
+ return Error(SizeLoc, "stack size must be a multiple of 8");
+ getStreamer().emitWinCFIAllocStack(static_cast<unsigned>(Size), Loc);
+ return false;
+}
+
+bool COFFMasmParser::parseSEHDirectiveFreeStack(StringRef Directive,
+ SMLoc Loc) {
+ if (ensureInEpilog(Loc, Directive))
+ return true;
int64_t Size;
SMLoc SizeLoc = getTok().getLoc();
if (getParser().parseAbsoluteExpression(Size))
return Error(SizeLoc, "expected integer size");
+ if (Size < 0)
+ return Error(SizeLoc, "stack size must be non-negative");
if (Size % 8 != 0)
return Error(SizeLoc, "stack size must be a multiple of 8");
getStreamer().emitWinCFIAllocStack(static_cast<unsigned>(Size), Loc);
@@ -531,10 +593,31 @@ bool COFFMasmParser::parseSEHDirectiveAllocStack(StringRef Directive,
bool COFFMasmParser::parseSEHDirectiveEndProlog(StringRef Directive,
SMLoc Loc) {
+ if (ensureInsideFrame(Loc, Directive))
+ return true;
getStreamer().emitWinCFIEndProlog(Loc);
return false;
}
+bool COFFMasmParser::ParseSEHDirectiveBeginEpilog(StringRef Directive,
+ SMLoc Loc) {
+ if (ensureInsideFrame(Loc, Directive))
+ return true;
+ if (getStreamer().isInEpilogCFI()) {
+ return Error(Loc, ".beginepilog must come after .endprolog or .endepilog");
+ }
+ getStreamer().emitWinCFIBeginEpilogue(Loc);
+ return false;
+}
+
+bool COFFMasmParser::ParseSEHDirectiveEndEpilog(StringRef Directive,
+ SMLoc Loc) {
+ if (ensureInsideFrame(Loc, Directive))
+ return true;
+ getStreamer().emitWinCFIEndEpilogue(Loc);
+ return false;
+}
+
MCAsmParserExtension *llvm::createCOFFMasmParser() {
return new COFFMasmParser;
}
diff --git a/llvm/lib/MC/MCParser/MasmParser.cpp b/llvm/lib/MC/MCParser/MasmParser.cpp
index 6ec95ef697b29..14443a63dda63 100644
--- a/llvm/lib/MC/MCParser/MasmParser.cpp
+++ b/llvm/lib/MC/MCParser/MasmParser.cpp
@@ -717,6 +717,7 @@ class MasmParser : public MCAsmParser {
DK_END,
DK_PUSHFRAME,
DK_PUSHREG,
+ DK_PUSH2REGS,
DK_SAVEREG,
DK_SAVEXMM128,
DK_SETFRAME,
@@ -749,6 +750,7 @@ class MasmParser : public MCAsmParser {
BI_DATASIZE,
BI_MODEL,
BI_STACK,
+ BI_UNWINDVERSION,
};
/// Maps builtin name --> BuiltinSymbol enum, for builtins handled by this
@@ -5306,9 +5308,15 @@ void MasmParser::initializeDirectiveKindMap() {
DirectiveKindMap[".errnz"] = DK_ERRNZ;
DirectiveKindMap[".pushframe"] = DK_PUSHFRAME;
DirectiveKindMap[".pushreg"] = DK_PUSHREG;
+ DirectiveKindMap[".push2reg"] = DK_PUSH2REGS;
+ DirectiveKindMap[".pop2reg"] = DK_PUSH2REGS;
+ DirectiveKindMap[".popreg"] = DK_PUSHREG;
DirectiveKindMap[".savereg"] = DK_SAVEREG;
+ DirectiveKindMap[".restorereg"] = DK_SAVEREG;
DirectiveKindMap[".savexmm128"] = DK_SAVEXMM128;
+ DirectiveKindMap[".restorexmm128"] = DK_SAVEXMM128;
DirectiveKindMap[".setframe"] = DK_SETFRAME;
+ DirectiveKindMap[".unsetframe"] = DK_SETFRAME;
DirectiveKindMap[".radix"] = DK_RADIX;
DirectiveKindMap["db"] = DK_DB;
DirectiveKindMap["dd"] = DK_DD;
@@ -6139,6 +6147,7 @@ void MasmParser::initializeBuiltinSymbolMaps() {
// Numeric built-ins (supported in all versions)
BuiltinSymbolMap["@version"] = BI_VERSION;
BuiltinSymbolMap["@line"] = BI_LINE;
+ BuiltinSymbolMap["@unwindversion"] = BI_UNWINDVERSION;
// Text built-ins (supported in all versions)
BuiltinSymbolMap["@date"] = BI_DATE;
@@ -6186,6 +6195,9 @@ const MCExpr *MasmParser::evaluateBuiltinValue(BuiltinSymbol Symbol,
ActiveMacros.front()->ExitBuffer);
return MCConstantExpr::create(Line, getContext());
}
+ case BI_UNWINDVERSION:
+ return MCConstantExpr::create(getStreamer().getDefaultWinCFIUnwindVersion(),
+ getContext());
}
llvm_unreachable("unhandled built-in symbol");
}
diff --git a/llvm/lib/Target/X86/AsmParser/X86AsmParser.cpp b/llvm/lib/Target/X86/AsmParser/X86AsmParser.cpp
index 58c6dd2b709a1..ab5c3440901c2 100644
--- a/llvm/lib/Target/X86/AsmParser/X86AsmParser.cpp
+++ b/llvm/lib/Target/X86/AsmParser/X86AsmParser.cpp
@@ -1232,12 +1232,15 @@ class X86AsmParser : public MCTargetAsmParser {
/// SEH directives.
bool parseSEHRegisterNumber(unsigned RegClassID, MCRegister &RegNo);
bool parseDirectiveSEHPushReg(SMLoc);
- bool parseDirectiveSEHPush2Regs(SMLoc);
+ bool parseDirectiveSEHPush2Regs(SMLoc, bool SwapRegs = false);
bool parseDirectiveSEHSetFrame(SMLoc);
bool parseDirectiveSEHSaveReg(SMLoc);
bool parseDirectiveSEHSaveXMM(SMLoc);
bool parseDirectiveSEHPushFrame(SMLoc);
+ bool ensureMasmEpilogContext(SMLoc Loc, StringRef Directive);
+ bool ensureMasmPrologContext(SMLoc Loc, StringRef Directive);
+
unsigned checkTargetMatchPredicate(MCInst &Inst) override;
bool validateInstruction(MCInst &Inst, const OperandVector &Ops);
@@ -4837,23 +4840,60 @@ bool X86AsmParser::ParseDirective(AsmToken DirectiveID) {
return parseDirectiveFPOEndPrologue(DirectiveID.getLoc());
else if (IDVal == ".cv_fpo_endproc")
return parseDirectiveFPOEndProc(DirectiveID.getLoc());
- else if (IDVal == ".seh_pushreg" ||
- (Parser.isParsingMasm() && IDVal.equals_insensitive(".pushreg")))
+ else if (IDVal == ".seh_pushreg")
return parseDirectiveSEHPushReg(DirectiveID.getLoc());
else if (IDVal == ".seh_push2regs")
return parseDirectiveSEHPush2Regs(DirectiveID.getLoc());
- else if (IDVal == ".seh_setframe" ||
- (Parser.isParsingMasm() && IDVal.equals_insensitive(".setframe")))
+ else if (IDVal == ".seh_setframe")
return parseDirectiveSEHSetFrame(DirectiveID.getLoc());
- else if (IDVal == ".seh_savereg" ||
- (Parser.isParsingMasm() && IDVal.equals_insensitive(".savereg")))
+ else if (IDVal == ".seh_savereg")
return parseDirectiveSEHSaveReg(DirectiveID.getLoc());
- else if (IDVal == ".seh_savexmm" ||
- (Parser.isParsingMasm() && IDVal.equals_insensitive(".savexmm128")))
+ else if (IDVal == ".seh_savexmm")
return parseDirectiveSEHSaveXMM(DirectiveID.getLoc());
- else if (IDVal == ".seh_pushframe" ||
- (Parser.isParsingMasm() && IDVal.equals_insensitive(".pushframe")))
+ else if (IDVal == ".seh_pushframe")
return parseDirectiveSEHPushFrame(DirectiveID.getLoc());
+ else if (Parser.isParsingMasm()) {
+ // MASM prolog directives.
+ if (IDVal.equals_insensitive(".pushreg")) {
+ return ensureMasmPrologContext(DirectiveID.getLoc(), IDVal) ||
+ parseDirectiveSEHPushReg(DirectiveID.getLoc());
+ } else if (IDVal.equals_insensitive(".push2reg")) {
+ return ensureMasmPrologContext(DirectiveID.getLoc(), IDVal) ||
+ parseDirectiveSEHPush2Regs(DirectiveID.getLoc());
+ } else if (IDVal.equals_insensitive(".setframe")) {
+ return ensureMasmPrologContext(DirectiveID.getLoc(), IDVal) ||
+ parseDirectiveSEHSetFrame(DirectiveID.getLoc());
+ } else if (IDVal.equals_insensitive(".savereg")) {
+ return ensureMasmPrologContext(DirectiveID.getLoc(), IDVal) ||
+ parseDirectiveSEHSaveReg(DirectiveID.getLoc());
+ } else if (IDVal.equals_insensitive(".savexmm128")) {
+ return ensureMasmPrologContext(DirectiveID.getLoc(), IDVal) ||
+ parseDirectiveSEHSaveXMM(DirectiveID.getLoc());
+ } else if (IDVal.equals_insensitive(".pushframe")) {
+ return ensureMasmPrologContext(DirectiveID.getLoc(), IDVal) ||
+ parseDirectiveSEHPushFrame(DirectiveID.getLoc());
+ }
+ // MASM epilog directives
+ if (IDVal.equals_insensitive(".popreg")) {
+ return ensureMasmEpilogContext(DirectiveID.getLoc(), IDVal) ||
+ parseDirectiveSEHPushReg(DirectiveID.getLoc());
+ } else if (IDVal.equals_insensitive(".pop2reg")) {
+ // .pop2reg args are in the order they are popped, so reverse them to get
+ // the order they were pushed.
+ return ensureMasmEpilogContext(DirectiveID.getLoc(), IDVal) ||
+ parseDirectiveSEHPush2Regs(DirectiveID.getLoc(),
+ /*SwapRegs=*/true);
+ } else if (IDVal.equals_insensitive(".unsetframe")) {
+ return ensureMasmEpilogContext(DirectiveID.getLoc(), IDVal) ||
+ parseDirectiveSEHSetFrame(DirectiveID.getLoc());
+ } else if (IDVal.equals_insensitive(".restorereg")) {
+ return ensureMasmEpilogContext(DirectiveID.getLoc(), IDVal) ||
+ parseDirectiveSEHSaveReg(DirectiveID.getLoc());
+ } else if (IDVal.equals_insensitive(".restorexmm128")) {
+ return ensureMasmEpilogContext(DirectiveID.getLoc(), IDVal) ||
+ parseDirectiveSEHSaveXMM(DirectiveID.getLoc());
+ }
+ }
return true;
}
@@ -5078,7 +5118,7 @@ bool X86AsmParser::parseDirectiveSEHPushReg(SMLoc Loc) {
return false;
}
-bool X86AsmParser::parseDirectiveSEHPush2Regs(SMLoc Loc) {
+bool X86AsmParser::parseDirectiveSEHPush2Regs(SMLoc Loc, bool SwapRegs) {
MCRegister Reg1;
if (parseSEHRegisterNumber(X86::GR64RegClassID, Reg1))
return true;
@@ -5095,6 +5135,9 @@ bool X86AsmParser::parseDirectiveSEHPush2Regs(SMLoc Loc) {
return TokError("expected end of directive");
getParser().Lex();
+ // Swap regs to go from pop order to push order.
+ if (SwapRegs)
+ std::swap(Reg1, Reg2);
getStreamer().emitWinCFIPush2Regs(Reg1, Reg2, Loc);
return false;
}
@@ -5159,6 +5202,20 @@ bool X86AsmParser::parseDirectiveSEHSaveXMM(SMLoc Loc) {
return false;
}
+bool X86AsmParser::ensureMasmPrologContext(SMLoc Loc, StringRef Directive) {
+ if (getStreamer().isWinCFIPrologEnded()) {
+ return Error(Loc, "prolog directive must be used inside a prolog");
+ }
+ return false;
+}
+
+bool X86AsmParser::ensureMasmEpilogContext(SMLoc Loc, StringRef Directive) {
+ if (!getStreamer().isInEpilogCFI()) {
+ return Error(Loc, "epilog directive must be used inside an epilog");
+ }
+ return false;
+}
+
bool X86AsmParser::parseDirectiveSEHPushFrame(SMLoc Loc) {
bool Code = false;
StringRef CodeID;
@@ -5170,6 +5227,11 @@ bool X86AsmParser::parseDirectiveSEHPushFrame(SMLoc Loc) {
return Error(startLoc, "expected @code");
Code = true;
}
+ } else if (getParser().isParsingMasm() &&
+ getLexer().is(AsmToken::Identifier) &&
+ getTok().getString().equals_insensitive("code")) {
+ getParser().Lex();
+ Code = true;
}
if (getLexer().isNot(AsmToken::EndOfStatement))
diff --git a/llvm/test/tools/llvm-ml/beginepilog_error.asm b/llvm/test/tools/llvm-ml/beginepilog_error.asm
new file mode 100644
index 0000000000000..c6bd86bd960e0
--- /dev/null
+++ b/llvm/test/tools/llvm-ml/beginepilog_error.asm
@@ -0,0 +1,19 @@
+; RUN: not llvm-ml64 -filetype=s /unwindv3 %s /Fo - 2>&1 | FileCheck %s
+
+.code
+
+; Test A2265: .beginepilog inside .beginepilog (before .endepilog)
+t1 PROC FRAME
+ push r10
+ .pushreg r10
+ .endprolog
+ .beginepilog
+ .popreg r10
+ pop r10
+; CHECK: .beginepilog must come after .endprolog or .endepilog
+ .beginepilog
+ .endepilog
+ ret
+t1 ENDP
+
+END
diff --git a/llvm/test/tools/llvm-ml/builtin_symbols.asm b/llvm/test/tools/llvm-ml/builtin_symbols.asm
index 81442c49d7ec7..e06f5a21efbbe 100644
--- a/llvm/test/tools/llvm-ml/builtin_symbols.asm
+++ b/llvm/test/tools/llvm-ml/builtin_symbols.asm
@@ -56,4 +56,10 @@ ECHO t6:
; CHECK-FIXEDTIME: Time = 00:00:00
; CHECK-NOT: {{[[:digit:]]}}
+ECHO t7:
+unwind_val TEXTEQU %@UnwindVersion
+%ECHO @UnwindVersion = unwind_val
+; CHECK-LABEL: t7:
+; CHECK-NEXT: @UnwindVersion = 1
+
end
diff --git a/llvm/test/tools/llvm-ml/epilog_directive_errors.asm b/llvm/test/tools/llvm-ml/epilog_directive_errors.asm
new file mode 100644
index 0000000000000..3ef71e25b534e
--- /dev/null
+++ b/llvm/test/tools/llvm-ml/epilog_directive_errors.asm
@@ -0,0 +1,42 @@
+; RUN: not llvm-ml64 -filetype=s /unwindv3 %s /Fo - 2>&1 | FileCheck %s
+
+.code
+
+; Test A2255: epilog directives used outside .beginepilog/.endepilog
+t1 PROC FRAME
+ push r12
+ .pushreg r12
+ sub rsp, 64
+ .allocstack 64
+ lea rbp, [rsp]
+ .setframe rbp, 0
+ movaps [rsp+16], xmm6
+ .savexmm128 xmm6, 16
+ mov [rsp+32], rbx
+ .savereg rbx, 32
+ .endprolog
+
+ mov rax, 0
+
+; CHECK: epilog directive must be used inside an epilog
+ .popreg r12
+
+; CHECK: epilog directive must be used inside an epilog
+ .pop2reg r12, r13
+
+; CHECK: epilog directive must be used inside an epilog
+ .freestack 64
+
+; CHECK: epilog directive must be used inside an epilog
+ .restorereg rbx, 32
+
+; CHECK: epilog directive must be used inside an epilog
+ .restorexmm128 xmm6, 16
+
+; CHECK: epilog directive must be used inside an epilog
+ .unsetframe rbp, 0
+
+ ret
+t1 ENDP
+
+END
diff --git a/llvm/test/tools/llvm-ml/epilog_directives.asm b/llvm/test/tools/llvm-ml/epilog_directives.asm
new file mode 100644
index 0000000000000..1f465a23a199a
--- /dev/null
+++ b/llvm/test/tools/llvm-ml/epilog_directives.asm
@@ -0,0 +1,278 @@
+; RUN: llvm-ml64 -filetype=s /unwindv3 %s /Fo - | FileCheck %s
+
+.code
+
+; Test .pushreg / .popreg
+t1 PROC FRAME
+ push r12
+ .pushreg r12
+ push r13
+ .pushreg r13
+ push rsi
+ .pushreg rsi
+ push rdi
+ .pushreg rdi
+ .endprolog
+ mov rax, 0
+ .beginepilog
+ .popreg rdi
+ pop rdi
+ .popreg rsi
+ pop rsi
+ .popreg r13
+ pop r13
+ .popreg r12
+ pop r12
+ .endepilog
+ ret
+t1 ENDP
+
+; CHECK: .seh_proc t1
+; CHECK: push r12
+; CHECK: .seh_pushreg r12
+; CHECK: push r13
+; CHECK: .seh_pushreg r13
+; CHECK: push rsi
+; CHECK: .seh_pushreg rsi
+; CHECK: push rdi
+; CHECK: .seh_pushreg rdi
+; CHECK: .seh_endprologue
+; CHECK: .seh_startepilogue
+; CHECK: .seh_pushreg rdi
+; CHECK: pop rdi
+; CHECK: .seh_pushreg rsi
+; CHECK: pop rsi
+; CHECK: .seh_pushreg r13
+; CHECK: pop r13
+; CHECK: .seh_pushreg r12
+; CHECK: pop r12
+; CHECK: .seh_endepilogue
+; CHECK: .seh_endproc
+
+; Test .allocstack / .freestack
+t2 PROC FRAME
+ push r12
+ .pushreg r12
+ sub rsp, 48
+ .allocstack 48
+ .endprolog
+ mov rax, 0
+ .beginepilog
+ .freestack 48
+ add rsp, 48
+ .popreg r12
+ pop r12
+ .endepilog
+ ret
+t2 ENDP
+
+; CHECK: .seh_proc t2
+; CHECK: .seh_pushreg r12
+; CHECK: .seh_stackalloc 48
+; CHECK: .seh_endprologue
+; CHECK: .seh_startepilogue
+; CHECK: .seh_stackalloc 48
+; CHECK: .seh_pushreg r12
+; CHECK: .seh_endepilogue
+; CHECK: .seh_endproc
+
+; Test .savereg / .restorereg
+t3 PROC FRAME
+ push r12
+ .pushreg r12
+ sub rsp, 48
+ .allocstack 48
+ mov [rsp], rbx
+ .savereg rbx, 0
+ mov [rsp+8], rsi
+ .savereg rsi, 8
+ .endprolog
+ mov rax, 0
+ .beginepilog
+ .restorereg rsi, 8
+ mov rsi, [rsp+8]
+ .restorereg rbx, 0
+ mov rbx, [rsp]
+ .freestack 48
+ add rsp, 48
+ .popreg r12
+ pop r12
+ .endepilog
+ ret
+t3 ENDP
+
+; CHECK: .seh_proc t3
+; CHECK: .seh_savereg rbx, 0
+; CHECK: .seh_savereg rsi, 8
+; CHECK: .seh_endprologue
+; CHECK: .seh_startepilogue
+; CHECK: .seh_savereg rsi, 8
+; CHECK: .seh_savereg rbx, 0
+; CHECK: .seh_stackalloc 48
+; CHECK: .seh_pushreg r12
+; CHECK: .seh_endepilogue
+; CHECK: .seh_endproc
+
+; Test .savexmm128 / .restorexmm128
+t4 PROC FRAME
+ push r12
+ .pushreg r12
+ sub rsp, 48
+ .allocstack 48
+ movaps [rsp], xmm6
+ .savexmm128 xmm6, 0
+ movaps [rsp+16], xmm7
+ .savexmm128 xmm7, 16
+ .endprolog
+ mov rax, 0
+ .beginepilog
+ .restorexmm128 xmm7, 16
+ movaps xmm7, [rsp+16]
+ .restorexmm128 xmm6, 0
+ movaps xmm6, [rsp]
+ .freestack 48
+ add rsp, 48
+ .popreg r12
+ pop r12
+ .endepilog
+ ret
+t4 ENDP
+
+; CHECK: .seh_proc t4
+; CHECK: .seh_savexmm xmm6, 0
+; CHECK: .seh_savexmm xmm7, 16
+; CHECK: .seh_endprologue
+; CHECK: .seh_startepilogue
+; CHECK: .seh_savexmm xmm7, 16
+; CHECK: .seh_savexmm xmm6, 0
+; CHECK: .seh_stackalloc 48
+; CHECK: .seh_pushreg r12
+; CHECK: .seh_endepilogue
+; CHECK: .seh_endproc
+
+; Test .setframe / .unsetframe
+t5 PROC FRAME
+ push r12
+ .pushreg r12
+ sub rsp, 32
+ .allocstack 32
+ lea rbp, [rsp+16]
+ .setframe rbp, 16
+ .endprolog
+ mov rax, 0
+ .beginepilog
+ .unsetframe rbp, 16
+ lea rsp, [rbp-16]
+ .freestack 32
+ add rsp, 32
+ .popreg r12
+ pop r12
+ .endepilog
+ ret
+t5 ENDP
+
+; CHECK: .seh_proc t5
+; CHECK: .seh_setframe rbp, 16
+; CHECK: .seh_endprologue
+; CHECK: .seh_startepilogue
+; CHECK: .seh_setframe rbp, 16
+; CHECK: .seh_stackalloc 32
+; CHECK: .seh_pushreg r12
+; CHECK: .seh_endepilogue
+; CHECK: .seh_endproc
+
+; Test .pushframe (interrupt handler)
+t6 PROC FRAME
+ .pushframe
+ push r12
+ .pushreg r12
+ sub rsp, 32
+ .allocstack 32
+ .endprolog
+ mov rax, 0
+ .beginepilog
+ .freestack 32
+ add rsp, 32
+ .popreg r12
+ pop r12
+ .endepilog
+ iretq
+t6 ENDP
+
+; CHECK: .seh_proc t6
+; CHECK: .seh_pushframe
+; CHECK: .seh_pushreg r12
+; CHECK: .seh_stackalloc 32
+; CHECK: .seh_endprologue
+; CHECK: .seh_startepilogue
+; CHECK: .seh_stackalloc 32
+; CHECK: .seh_pushreg r12
+; CHECK: .seh_endepilogue
+; CHECK: .seh_endproc
+
+; Test .pushframe code (interrupt handler with error code)
+t7 PROC FRAME
+ .pushframe code
+ push r12
+ .pushreg r12
+ sub rsp, 32
+ .allocstack 32
+ .endprolog
+ mov rax, 0
+ .beginepilog
+ .freestack 32
+ add rsp, 32
+ .popreg r12
+ pop r12
+ .endepilog
+ iretq
+t7 ENDP
+
+; CHECK: .seh_proc t7
+; CHECK: .seh_pushframe @code
+; CHECK: .seh_endproc
+
+; Test two epilogs
+t8 PROC FRAME
+ push r12
+ .pushreg r12
+ push rdi
+ .pushreg rdi
+ .endprolog
+ mov rax, 0
+ cmp rcx, 0
+ je epilog2
+ .beginepilog
+ .popreg rdi
+ pop rdi
+ .popreg r12
+ pop r12
+ .endepilog
+ ret
+epilog2:
+ .beginepilog
+ .popreg rdi
+ pop rdi
+ .popreg r12
+ pop r12
+ .endepilog
+ ret
+t8 ENDP
+
+; CHECK: .seh_proc t8
+; CHECK: .seh_pushreg r12
+; CHECK: .seh_pushreg rdi
+; CHECK: .seh_endprologue
+; CHECK: .seh_startepilogue
+; CHECK: .seh_pushreg rdi
+; CHECK: .seh_pushreg r12
+; CHECK: .seh_endepilogue
+; CHECK: ret
+; CHECK: .seh_startepilogue
+; CHECK: .seh_pushreg rdi
+; CHECK: .seh_pushreg r12
+; CHECK: .seh_endepilogue
+; CHECK: ret
+; CHECK: .seh_endproc
+
+END
diff --git a/llvm/test/tools/llvm-ml/proc_frame_v3.asm b/llvm/test/tools/llvm-ml/proc_frame_v3.asm
new file mode 100644
index 0000000000000..1e9c57cd39c3d
--- /dev/null
+++ b/llvm/test/tools/llvm-ml/proc_frame_v3.asm
@@ -0,0 +1,177 @@
+; RUN: llvm-ml64 -filetype=s --mattr=+push2pop2 /unwindv3 %s /Fo - | FileCheck %s
+
+.code
+
+; Verify @UnwindVersion reflects the /unwindv3 flag.
+if @UnwindVersion ne 3
+ .err <@UnwindVersion should be 3 when /unwindv3 is used>
+endif
+
+t1 PROC FRAME
+ push2 r12, r13
+ .push2reg r12, r13
+ sub rsp, 32
+ .allocstack 32
+ .endprolog
+ nop
+ .beginepilog
+ .freestack 32
+ add rsp, 32
+ .pop2reg r12, r13
+ pop2 r13, r12
+ .endepilog
+ ret
+t1 ENDP
+
+; CHECK: .seh_proc t1
+
+; CHECK: t1:
+; CHECK: push2 r12, r13
+; CHECK: .seh_push2regs r12, r13
+; CHECK: sub rsp, 32
+; CHECK: .seh_stackalloc 32
+; CHECK: .seh_endprologue
+; CHECK: nop
+; CHECK: .seh_startepilogue
+; CHECK: .seh_stackalloc 32
+; CHECK: add rsp, 32
+; CHECK: .seh_push2regs r13, r12
+; CHECK: pop2 r13, r12
+; CHECK: .seh_endepilogue
+; CHECK: ret
+; CHECK: .seh_endproc
+
+; Test .popreg
+t2 PROC FRAME
+ push r12
+ .pushreg r12
+ .endprolog
+ nop
+ .beginepilog
+ .popreg r12
+ pop r12
+ .endepilog
+ ret
+t2 ENDP
+
+; CHECK: .seh_proc t2
+; CHECK: .seh_pushreg r12
+; CHECK: .seh_endprologue
+; CHECK: .seh_startepilogue
+; CHECK: .seh_pushreg r12
+; CHECK: .seh_endepilogue
+; CHECK: .seh_endproc
+
+; Test .freestack
+t3 PROC FRAME
+ push r12
+ .pushreg r12
+ sub rsp, 32
+ .allocstack 32
+ .endprolog
+ nop
+ .beginepilog
+ .freestack 32
+ add rsp, 32
+ .popreg r12
+ pop r12
+ .endepilog
+ ret
+t3 ENDP
+
+; CHECK: .seh_proc t3
+; CHECK: .seh_stackalloc 32
+; CHECK: .seh_endprologue
+; CHECK: .seh_startepilogue
+; CHECK: .seh_stackalloc 32
+; CHECK: .seh_endepilogue
+; CHECK: .seh_endproc
+
+; Test .restorereg
+t4 PROC FRAME
+ push r12
+ .pushreg r12
+ sub rsp, 32
+ .allocstack 32
+ mov [rsp], rbx
+ .savereg rbx, 0
+ .endprolog
+ nop
+ .beginepilog
+ .restorereg rbx, 0
+ mov rbx, [rsp]
+ .freestack 32
+ add rsp, 32
+ .popreg r12
+ pop r12
+ .endepilog
+ ret
+t4 ENDP
+
+; CHECK: .seh_proc t4
+; CHECK: .seh_savereg rbx, 0
+; CHECK: .seh_endprologue
+; CHECK: .seh_startepilogue
+; CHECK: .seh_savereg rbx, 0
+; CHECK: .seh_endepilogue
+; CHECK: .seh_endproc
+
+; Test .restorexmm128
+t5 PROC FRAME
+ push r12
+ .pushreg r12
+ sub rsp, 32
+ .allocstack 32
+ movaps [rsp], xmm6
+ .savexmm128 xmm6, 0
+ .endprolog
+ nop
+ .beginepilog
+ .restorexmm128 xmm6, 0
+ movaps xmm6, [rsp]
+ .freestack 32
+ add rsp, 32
+ .popreg r12
+ pop r12
+ .endepilog
+ ret
+t5 ENDP
+
+; CHECK: .seh_proc t5
+; CHECK: .seh_savexmm xmm6, 0
+; CHECK: .seh_endprologue
+; CHECK: .seh_startepilogue
+; CHECK: .seh_savexmm xmm6, 0
+; CHECK: .seh_endepilogue
+; CHECK: .seh_endproc
+
+; Test .unsetframe
+t6 PROC FRAME
+ push r12
+ .pushreg r12
+ sub rsp, 32
+ .allocstack 32
+ lea rbp, [rsp+16]
+ .setframe rbp, 16
+ .endprolog
+ nop
+ .beginepilog
+ .unsetframe rbp, 16
+ lea rsp, [rbp-16]
+ .freestack 32
+ add rsp, 32
+ .popreg r12
+ pop r12
+ .endepilog
+ ret
+t6 ENDP
+
+; CHECK: .seh_proc t6
+; CHECK: .seh_setframe rbp, 16
+; CHECK: .seh_endprologue
+; CHECK: .seh_startepilogue
+; CHECK: .seh_setframe rbp, 16
+; CHECK: .seh_endepilogue
+; CHECK: .seh_endproc
+
+END
diff --git a/llvm/test/tools/llvm-ml/prolog_directive_errors.asm b/llvm/test/tools/llvm-ml/prolog_directive_errors.asm
new file mode 100644
index 0000000000000..dcdeaa94f7899
--- /dev/null
+++ b/llvm/test/tools/llvm-ml/prolog_directive_errors.asm
@@ -0,0 +1,45 @@
+; RUN: not llvm-ml64 -filetype=s /unwindv3 %s /Fo - 2>&1 | FileCheck %s
+
+.code
+
+; Test A2256: prolog directives used after .endprolog
+t1 PROC FRAME
+ push r12
+ .pushreg r12
+ sub rsp, 32
+ .allocstack 32
+ .endprolog
+
+ mov rax, 0
+
+; CHECK: prolog directive must be used inside a prolog
+ .pushreg r13
+
+; CHECK: prolog directive must be used inside a prolog
+ .push2reg r14, r15
+
+; CHECK: prolog directive must be used inside a prolog
+ .pushframe
+
+; CHECK: prolog directive must be used inside a prolog
+ .setframe rbp, 0
+
+; CHECK: prolog directive must be used inside a prolog
+ .allocstack 16
+
+; CHECK: prolog directive must be used inside a prolog
+ .savereg rbx, 0
+
+; CHECK: prolog directive must be used inside a prolog
+ .savexmm128 xmm6, 0
+
+ .beginepilog
+ .freestack 32
+ add rsp, 32
+ .popreg r12
+ pop r12
+ .endepilog
+ ret
+t1 ENDP
+
+END
diff --git a/llvm/tools/llvm-ml/Opts.td b/llvm/tools/llvm-ml/Opts.td
index 8b748bb64b278..121c8cfeef566 100644
--- a/llvm/tools/llvm-ml/Opts.td
+++ b/llvm/tools/llvm-ml/Opts.td
@@ -41,6 +41,8 @@ def fatal_warnings : LLVMFlag<"fatal-warnings">,
HelpText<"Treat warnings as errors">;
def filetype : LLVMJoined<"filetype=">, Values<"obj,s,null">,
HelpText<"Emit a file with the given type">;
+def mattr : LLVMCommaJoined<"mattr=">,
+ HelpText<"Target specific attributes (--mattr=help for details)">;
def output_att_asm : LLVMFlag<"output-att-asm">,
HelpText<"Use ATT syntax for output assembly">;
def show_encoding : LLVMFlag<"show-encoding">,
@@ -81,6 +83,9 @@ def safeseh : MLFlag<"safeseh">,
"exception handlers or containing exception handlers "
"that are all declared with .SAFESEH. Only available in "
"32-bit.">;
+def unwindv3 : MLFlag<"unwindv3">,
+ HelpText<"Use V3 unwind information format for x64 exception "
+ "handling.">;
def assembly_file : MLJoinedOrSeparate<"Ta">,
HelpText<"Assemble source file with the given name. Used "
"if the filename begins with a forward slash.">;
diff --git a/llvm/tools/llvm-ml/llvm-ml.cpp b/llvm/tools/llvm-ml/llvm-ml.cpp
index 6ecc1bf86ef50..b5670e6bf1de6 100644
--- a/llvm/tools/llvm-ml/llvm-ml.cpp
+++ b/llvm/tools/llvm-ml/llvm-ml.cpp
@@ -10,6 +10,7 @@
//
//===----------------------------------------------------------------------===//
+#include "llvm/ADT/StringExtras.h"
#include "llvm/ADT/StringSwitch.h"
#include "llvm/MC/MCAsmBackend.h"
#include "llvm/MC/MCAsmInfo.h"
@@ -44,6 +45,7 @@
#include "llvm/Support/VirtualFileSystem.h"
#include "llvm/Support/WithColor.h"
#include "llvm/TargetParser/Host.h"
+#include "llvm/TargetParser/SubtargetFeature.h"
#include <ctime>
#include <optional>
@@ -285,6 +287,13 @@ int llvm_ml_main(int Argc, char **Argv, const llvm::ToolContext &) {
SafeSEH = false;
}
+ bool UnwindV3 = InputArgs.hasArg(OPT_unwindv3);
+ if (UnwindV3 && !TheTriple.isArch64Bit()) {
+ WithColor::warning()
+ << "/unwindv3 applies only to 64-bit X86 platforms; ignoring.\n";
+ UnwindV3 = false;
+ }
+
ErrorOr<std::unique_ptr<MemoryBuffer>> BufferPtr =
MemoryBuffer::getFileOrSTDIN(InputFilename);
if (std::error_code EC = BufferPtr.getError()) {
@@ -325,8 +334,17 @@ int llvm_ml_main(int Argc, char **Argv, const llvm::ToolContext &) {
MAI->setPreserveAsmComments(InputArgs.hasArg(OPT_preserve_comments));
- std::unique_ptr<MCSubtargetInfo> STI(
- TheTarget->createMCSubtargetInfo(TheTriple, /*CPU=*/"", /*Features=*/""));
+ std::string FeaturesStr;
+ if (InputArgs.hasArg(OPT_mattr)) {
+ SubtargetFeatures Features;
+ for (auto *A : InputArgs.filtered(OPT_mattr))
+ for (StringRef F : llvm::split(A->getValue(), ','))
+ Features.AddFeature(F);
+ FeaturesStr = Features.getString();
+ }
+
+ std::unique_ptr<MCSubtargetInfo> STI(TheTarget->createMCSubtargetInfo(
+ TheTriple, /*CPU=*/"", /*Features=*/FeaturesStr));
if (!STI) {
WithColor::error(errs(), ProgName) << "unable to create subtarget info\n";
exit(1);
@@ -430,6 +448,9 @@ int llvm_ml_main(int Argc, char **Argv, const llvm::ToolContext &) {
Str->emitAssignment(Feat00Sym, MCConstantExpr::create(Feat00Flags, Ctx));
}
+ if (UnwindV3)
+ Str->setDefaultWinCFIUnwindVersion(3);
+
int Res = 1;
if (InputArgs.hasArg(OPT_as_lex)) {
// -as-lex; Lex only, and output a stream of tokens
>From 3c7fe9bc6731c50c6ab1cc50d2f68cc377bd2de1 Mon Sep 17 00:00:00 2001
From: Daniel Paoliello <danpao at microsoft.com>
Date: Wed, 10 Jun 2026 11:20:23 -0700
Subject: [PATCH 2/3] Addressed PR feedback: remove -mattr, errors for
push2pop2 or epgr without unwindv3
---
llvm/docs/ReleaseNotes.md | 2 -
llvm/lib/MC/MCStreamer.cpp | 29 +++++++++
llvm/test/MC/COFF/seh-unwindv3-error.s | 36 +++++++++++
llvm/test/tools/llvm-ml/proc_frame_v3.asm | 2 +-
.../llvm-ml/unwindv3_required_errors.asm | 61 +++++++++++++++++++
llvm/tools/llvm-ml/Opts.td | 2 -
llvm/tools/llvm-ml/llvm-ml.cpp | 15 +----
7 files changed, 129 insertions(+), 18 deletions(-)
create mode 100644 llvm/test/tools/llvm-ml/unwindv3_required_errors.asm
diff --git a/llvm/docs/ReleaseNotes.md b/llvm/docs/ReleaseNotes.md
index ba042577c84ac..68075fd71030d 100644
--- a/llvm/docs/ReleaseNotes.md
+++ b/llvm/docs/ReleaseNotes.md
@@ -321,8 +321,6 @@ Makes programs 10x faster by doing Special New Thing.
### Changes to the LLVM tools
-* llvm-ml now supports the `--mattr` flag for enabling target-specific CPU
- features (e.g. `--mattr=+push2pop2`).
* llvm-ml now supports the `/unwindv3` flag to enable V3 unwind information
format for x64 exception handling.
* llvm-ml now supports the `@UnwindVersion` built-in symbol, which returns the
diff --git a/llvm/lib/MC/MCStreamer.cpp b/llvm/lib/MC/MCStreamer.cpp
index a02ba1fe31f8c..d6542ef24a9e4 100644
--- a/llvm/lib/MC/MCStreamer.cpp
+++ b/llvm/lib/MC/MCStreamer.cpp
@@ -955,6 +955,23 @@ static unsigned encodeSEHRegNum(MCContext &Ctx, MCRegister Reg) {
return Ctx.getRegisterInfo()->getSEHRegNum(Reg);
}
+// Unwind formats before v3 store the register operand of an unwind code in a
+// 4-bit field, so extended registers (r16-r31 / xmm16-xmm31, i.e. SEH register
+// numbers greater than 15) cannot be represented. Report an error rather than
+// silently truncating the register number to a different register. Returns true
+// if an error was reported.
+static bool checkUnwindV3ExtendedReg(MCContext &Ctx,
+ const WinEH::Instruction &Inst,
+ uint8_t Version, SMLoc Loc,
+ StringRef Directive) {
+ if (Version < 3 && Inst.Register > 15) {
+ Ctx.reportError(Loc, Directive +
+ " with an extended register requires unwind v3");
+ return true;
+ }
+ return false;
+}
+
void MCStreamer::emitWinCFIPushReg(MCRegister Register, SMLoc Loc) {
WinEH::FrameInfo *CurFrame = EnsureValidWinFrameInfo(Loc);
if (!CurFrame)
@@ -970,6 +987,9 @@ void MCStreamer::emitWinCFIPushReg(MCRegister Register, SMLoc Loc) {
Loc, ".seh_pushreg inside epilog requires unwind v3");
CurrentWinEpilog->Instructions.push_back(Inst);
} else {
+ if (checkUnwindV3ExtendedReg(getContext(), Inst, CurFrame->Version, Loc,
+ ".seh_pushreg"))
+ return;
CurFrame->Instructions.push_back(Inst);
}
}
@@ -1019,6 +1039,9 @@ void MCStreamer::emitWinCFISetFrame(MCRegister Register, unsigned Offset,
Loc, ".seh_setframe inside epilog requires unwind v3");
CurrentWinEpilog->Instructions.push_back(Inst);
} else {
+ if (checkUnwindV3ExtendedReg(getContext(), Inst, CurFrame->Version, Loc,
+ ".seh_setframe"))
+ return;
CurFrame->LastFrameInst = CurFrame->Instructions.size();
CurFrame->Instructions.push_back(Inst);
}
@@ -1068,6 +1091,9 @@ void MCStreamer::emitWinCFISaveReg(MCRegister Register, unsigned Offset,
Loc, ".seh_savereg inside epilog requires unwind v3");
CurrentWinEpilog->Instructions.push_back(Inst);
} else {
+ if (checkUnwindV3ExtendedReg(getContext(), Inst, CurFrame->Version, Loc,
+ ".seh_savereg"))
+ return;
CurFrame->Instructions.push_back(Inst);
}
}
@@ -1090,6 +1116,9 @@ void MCStreamer::emitWinCFISaveXMM(MCRegister Register, unsigned Offset,
Loc, ".seh_savexmm inside epilog requires unwind v3");
CurrentWinEpilog->Instructions.push_back(Inst);
} else {
+ if (checkUnwindV3ExtendedReg(getContext(), Inst, CurFrame->Version, Loc,
+ ".seh_savexmm"))
+ return;
CurFrame->Instructions.push_back(Inst);
}
}
diff --git a/llvm/test/MC/COFF/seh-unwindv3-error.s b/llvm/test/MC/COFF/seh-unwindv3-error.s
index 0cc0e13b172bc..a8bd775870c6c 100644
--- a/llvm/test/MC/COFF/seh-unwindv3-error.s
+++ b/llvm/test/MC/COFF/seh-unwindv3-error.s
@@ -49,6 +49,41 @@ push2_trailing_junk:
retq
.seh_endproc
+// Test: extended registers (r16-r31 / xmm16-xmm31) in prolog unwind codes
+// cannot be encoded in V1/V2 (the register field is only 4 bits), so they must
+// be diagnosed rather than silently truncated to a low register.
+pushreg_egpr_v1:
+ .seh_proc pushreg_egpr_v1
+ .seh_pushreg %r16
+// CHECK: error: .seh_pushreg with an extended register requires unwind v3
+ .seh_endprologue
+ retq
+ .seh_endproc
+
+savereg_egpr_v1:
+ .seh_proc savereg_egpr_v1
+ .seh_savereg %r16, 0
+// CHECK: error: .seh_savereg with an extended register requires unwind v3
+ .seh_endprologue
+ retq
+ .seh_endproc
+
+savexmm_egpr_v1:
+ .seh_proc savexmm_egpr_v1
+ .seh_savexmm %xmm16, 0
+// CHECK: error: .seh_savexmm with an extended register requires unwind v3
+ .seh_endprologue
+ retq
+ .seh_endproc
+
+setframe_egpr_v1:
+ .seh_proc setframe_egpr_v1
+ .seh_setframe %r16, 0
+// CHECK: error: .seh_setframe with an extended register requires unwind v3
+ .seh_endprologue
+ retq
+ .seh_endproc
+
// Test: UOP_Push2 recorded under V3 then frame downgraded to V2 — the
// directive-level check passes (since the frame is already V3), so the
// error must come from the unwind-info emitter as a recoverable diagnostic
@@ -70,3 +105,4 @@ push2_downgrade_v2:
retq
.seh_endproc
// CHECK: error: UOP_Push2 (PUSH2 with two registers) requires V3 unwind info
+
diff --git a/llvm/test/tools/llvm-ml/proc_frame_v3.asm b/llvm/test/tools/llvm-ml/proc_frame_v3.asm
index 1e9c57cd39c3d..d3e2112455e89 100644
--- a/llvm/test/tools/llvm-ml/proc_frame_v3.asm
+++ b/llvm/test/tools/llvm-ml/proc_frame_v3.asm
@@ -1,4 +1,4 @@
-; RUN: llvm-ml64 -filetype=s --mattr=+push2pop2 /unwindv3 %s /Fo - | FileCheck %s
+; RUN: llvm-ml64 -filetype=s /unwindv3 %s /Fo - | FileCheck %s
.code
diff --git a/llvm/test/tools/llvm-ml/unwindv3_required_errors.asm b/llvm/test/tools/llvm-ml/unwindv3_required_errors.asm
new file mode 100644
index 0000000000000..54ebe8fd7007b
--- /dev/null
+++ b/llvm/test/tools/llvm-ml/unwindv3_required_errors.asm
@@ -0,0 +1,61 @@
+; RUN: not llvm-ml64 -filetype=s %s /Fo - 2>&1 | FileCheck %s
+
+; These directives require unwind v3, but this file is assembled WITHOUT the
+; /unwindv3 flag (so the default unwind version is 1). Each use must be
+; diagnosed rather than silently mis-encoded.
+
+.code
+
+; .push2reg / .pop2reg map to UOP_Push2, which only exists in v3.
+t1 PROC FRAME
+ push2 r12, r13
+ .push2reg r12, r13
+; CHECK: error: .seh_push2regs is only supported for unwind v3
+ .endprolog
+ ret
+t1 ENDP
+
+; Epilog unwind codes only exist in v3.
+t2 PROC FRAME
+ push r12
+ .pushreg r12
+ sub rsp, 32
+ .allocstack 32
+ .endprolog
+ nop
+ .beginepilog
+ .freestack 32
+; CHECK: error: .seh_stackalloc inside epilog requires unwind v3
+ add rsp, 32
+ .popreg r12
+; CHECK: error: .seh_pushreg inside epilog requires unwind v3
+ pop r12
+ .endepilog
+ ret
+t2 ENDP
+
+; Extended registers (r16-r31 / xmm16-xmm31) do not fit in the 4-bit register
+; field of a v1/v2 unwind code, so they must be diagnosed rather than truncated
+; to a low register.
+t3 PROC FRAME
+ push r16
+ .pushreg r16
+; CHECK: error: .seh_pushreg with an extended register requires unwind v3
+ .endprolog
+ ret
+t3 ENDP
+
+t4 PROC FRAME
+ sub rsp, 32
+ .allocstack 32
+ .savereg r16, 0
+; CHECK: error: .seh_savereg with an extended register requires unwind v3
+ .savexmm128 xmm16, 16
+; CHECK: error: .seh_savexmm with an extended register requires unwind v3
+ .setframe r17, 0
+; CHECK: error: .seh_setframe with an extended register requires unwind v3
+ .endprolog
+ ret
+t4 ENDP
+
+END
diff --git a/llvm/tools/llvm-ml/Opts.td b/llvm/tools/llvm-ml/Opts.td
index 121c8cfeef566..fb08f4033c6b6 100644
--- a/llvm/tools/llvm-ml/Opts.td
+++ b/llvm/tools/llvm-ml/Opts.td
@@ -41,8 +41,6 @@ def fatal_warnings : LLVMFlag<"fatal-warnings">,
HelpText<"Treat warnings as errors">;
def filetype : LLVMJoined<"filetype=">, Values<"obj,s,null">,
HelpText<"Emit a file with the given type">;
-def mattr : LLVMCommaJoined<"mattr=">,
- HelpText<"Target specific attributes (--mattr=help for details)">;
def output_att_asm : LLVMFlag<"output-att-asm">,
HelpText<"Use ATT syntax for output assembly">;
def show_encoding : LLVMFlag<"show-encoding">,
diff --git a/llvm/tools/llvm-ml/llvm-ml.cpp b/llvm/tools/llvm-ml/llvm-ml.cpp
index b5670e6bf1de6..6f181a6c5d6bb 100644
--- a/llvm/tools/llvm-ml/llvm-ml.cpp
+++ b/llvm/tools/llvm-ml/llvm-ml.cpp
@@ -10,7 +10,6 @@
//
//===----------------------------------------------------------------------===//
-#include "llvm/ADT/StringExtras.h"
#include "llvm/ADT/StringSwitch.h"
#include "llvm/MC/MCAsmBackend.h"
#include "llvm/MC/MCAsmInfo.h"
@@ -45,7 +44,6 @@
#include "llvm/Support/VirtualFileSystem.h"
#include "llvm/Support/WithColor.h"
#include "llvm/TargetParser/Host.h"
-#include "llvm/TargetParser/SubtargetFeature.h"
#include <ctime>
#include <optional>
@@ -334,17 +332,8 @@ int llvm_ml_main(int Argc, char **Argv, const llvm::ToolContext &) {
MAI->setPreserveAsmComments(InputArgs.hasArg(OPT_preserve_comments));
- std::string FeaturesStr;
- if (InputArgs.hasArg(OPT_mattr)) {
- SubtargetFeatures Features;
- for (auto *A : InputArgs.filtered(OPT_mattr))
- for (StringRef F : llvm::split(A->getValue(), ','))
- Features.AddFeature(F);
- FeaturesStr = Features.getString();
- }
-
- std::unique_ptr<MCSubtargetInfo> STI(TheTarget->createMCSubtargetInfo(
- TheTriple, /*CPU=*/"", /*Features=*/FeaturesStr));
+ std::unique_ptr<MCSubtargetInfo> STI(
+ TheTarget->createMCSubtargetInfo(TheTriple, /*CPU=*/"", /*Features=*/""));
if (!STI) {
WithColor::error(errs(), ProgName) << "unable to create subtarget info\n";
exit(1);
>From a6a47ca025b7ab59bab8b74d8aaf94afca5110b4 Mon Sep 17 00:00:00 2001
From: Daniel Paoliello <danpao at microsoft.com>
Date: Thu, 11 Jun 2026 09:16:38 -0700
Subject: [PATCH 3/3] Addressed PR feedback
---
llvm/lib/MC/MCParser/COFFMasmParser.cpp | 44 +++++++++----------
.../lib/Target/X86/AsmParser/X86AsmParser.cpp | 30 ++++++-------
llvm/test/tools/llvm-ml/proc_frame_v3.asm | 4 +-
3 files changed, 39 insertions(+), 39 deletions(-)
diff --git a/llvm/lib/MC/MCParser/COFFMasmParser.cpp b/llvm/lib/MC/MCParser/COFFMasmParser.cpp
index 7ba3aa0256190..4e899f980a535 100644
--- a/llvm/lib/MC/MCParser/COFFMasmParser.cpp
+++ b/llvm/lib/MC/MCParser/COFFMasmParser.cpp
@@ -51,15 +51,15 @@ class COFFMasmParser : public MCAsmParserExtension {
bool parseSEHDirectiveAllocStack(StringRef, SMLoc);
bool parseSEHDirectiveFreeStack(StringRef, SMLoc);
bool parseSEHDirectiveEndProlog(StringRef, SMLoc);
- bool ParseSEHDirectiveBeginEpilog(StringRef, SMLoc);
- bool ParseSEHDirectiveEndEpilog(StringRef, SMLoc);
+ bool parseSEHDirectiveBeginEpilog(StringRef, SMLoc);
+ bool parseSEHDirectiveEndEpilog(StringRef, SMLoc);
/// Check that we are inside a PROC FRAME.
- bool ensureInsideFrame(SMLoc Loc, StringRef Directive);
+ bool ensureInsideFrame(SMLoc Loc);
/// Check that we are in the prolog (before .endprolog).
- bool ensureInProlog(SMLoc Loc, StringRef Directive);
+ bool ensureInProlog(SMLoc Loc);
/// Check that we are inside a .beginepilog/.endepilog block.
- bool ensureInEpilog(SMLoc Loc, StringRef Directive);
+ bool ensureInEpilog(SMLoc Loc);
bool IgnoreDirective(StringRef, SMLoc) {
while (!getLexer().is(AsmToken::EndOfStatement)) {
@@ -79,9 +79,9 @@ class COFFMasmParser : public MCAsmParserExtension {
".freestack");
addDirectiveHandler<&COFFMasmParser::parseSEHDirectiveEndProlog>(
".endprolog");
- addDirectiveHandler<&COFFMasmParser::ParseSEHDirectiveBeginEpilog>(
+ addDirectiveHandler<&COFFMasmParser::parseSEHDirectiveBeginEpilog>(
".beginepilog");
- addDirectiveHandler<&COFFMasmParser::ParseSEHDirectiveEndEpilog>(
+ addDirectiveHandler<&COFFMasmParser::parseSEHDirectiveEndEpilog>(
".endepilog");
// Code label directives
@@ -533,7 +533,7 @@ bool COFFMasmParser::parseDirectiveAlias(StringRef Directive, SMLoc Loc) {
return false;
}
-bool COFFMasmParser::ensureInsideFrame(SMLoc Loc, StringRef Directive) {
+bool COFFMasmParser::ensureInsideFrame(SMLoc Loc) {
if (CurrentProceduresFramed.empty() || !CurrentProceduresFramed.back()) {
return Error(Loc,
"Missing Frame in proc, no unwind code will be generated.");
@@ -541,8 +541,8 @@ bool COFFMasmParser::ensureInsideFrame(SMLoc Loc, StringRef Directive) {
return false;
}
-bool COFFMasmParser::ensureInProlog(SMLoc Loc, StringRef Directive) {
- if (ensureInsideFrame(Loc, Directive))
+bool COFFMasmParser::ensureInProlog(SMLoc Loc) {
+ if (ensureInsideFrame(Loc))
return true;
if (getStreamer().isWinCFIPrologEnded()) {
return Error(Loc, "prolog directive must be used inside a prolog");
@@ -550,8 +550,8 @@ bool COFFMasmParser::ensureInProlog(SMLoc Loc, StringRef Directive) {
return false;
}
-bool COFFMasmParser::ensureInEpilog(SMLoc Loc, StringRef Directive) {
- if (ensureInsideFrame(Loc, Directive))
+bool COFFMasmParser::ensureInEpilog(SMLoc Loc) {
+ if (ensureInsideFrame(Loc))
return true;
if (!getStreamer().isInEpilogCFI()) {
return Error(Loc, "epilog directive must be used inside an epilog");
@@ -559,9 +559,9 @@ bool COFFMasmParser::ensureInEpilog(SMLoc Loc, StringRef Directive) {
return false;
}
-bool COFFMasmParser::parseSEHDirectiveAllocStack(StringRef Directive,
+bool COFFMasmParser::parseSEHDirectiveAllocStack(StringRef /*Directive*/,
SMLoc Loc) {
- if (ensureInProlog(Loc, Directive))
+ if (ensureInProlog(Loc))
return true;
int64_t Size;
SMLoc SizeLoc = getTok().getLoc();
@@ -575,9 +575,9 @@ bool COFFMasmParser::parseSEHDirectiveAllocStack(StringRef Directive,
return false;
}
-bool COFFMasmParser::parseSEHDirectiveFreeStack(StringRef Directive,
+bool COFFMasmParser::parseSEHDirectiveFreeStack(StringRef /*Directive*/,
SMLoc Loc) {
- if (ensureInEpilog(Loc, Directive))
+ if (ensureInEpilog(Loc))
return true;
int64_t Size;
SMLoc SizeLoc = getTok().getLoc();
@@ -591,17 +591,17 @@ bool COFFMasmParser::parseSEHDirectiveFreeStack(StringRef Directive,
return false;
}
-bool COFFMasmParser::parseSEHDirectiveEndProlog(StringRef Directive,
+bool COFFMasmParser::parseSEHDirectiveEndProlog(StringRef /*Directive*/,
SMLoc Loc) {
- if (ensureInsideFrame(Loc, Directive))
+ if (ensureInsideFrame(Loc))
return true;
getStreamer().emitWinCFIEndProlog(Loc);
return false;
}
-bool COFFMasmParser::ParseSEHDirectiveBeginEpilog(StringRef Directive,
+bool COFFMasmParser::parseSEHDirectiveBeginEpilog(StringRef /*Directive*/,
SMLoc Loc) {
- if (ensureInsideFrame(Loc, Directive))
+ if (ensureInsideFrame(Loc))
return true;
if (getStreamer().isInEpilogCFI()) {
return Error(Loc, ".beginepilog must come after .endprolog or .endepilog");
@@ -610,9 +610,9 @@ bool COFFMasmParser::ParseSEHDirectiveBeginEpilog(StringRef Directive,
return false;
}
-bool COFFMasmParser::ParseSEHDirectiveEndEpilog(StringRef Directive,
+bool COFFMasmParser::parseSEHDirectiveEndEpilog(StringRef /*Directive*/,
SMLoc Loc) {
- if (ensureInsideFrame(Loc, Directive))
+ if (ensureInsideFrame(Loc))
return true;
getStreamer().emitWinCFIEndEpilogue(Loc);
return false;
diff --git a/llvm/lib/Target/X86/AsmParser/X86AsmParser.cpp b/llvm/lib/Target/X86/AsmParser/X86AsmParser.cpp
index ab5c3440901c2..33ce25425b29e 100644
--- a/llvm/lib/Target/X86/AsmParser/X86AsmParser.cpp
+++ b/llvm/lib/Target/X86/AsmParser/X86AsmParser.cpp
@@ -1238,8 +1238,8 @@ class X86AsmParser : public MCTargetAsmParser {
bool parseDirectiveSEHSaveXMM(SMLoc);
bool parseDirectiveSEHPushFrame(SMLoc);
- bool ensureMasmEpilogContext(SMLoc Loc, StringRef Directive);
- bool ensureMasmPrologContext(SMLoc Loc, StringRef Directive);
+ bool ensureMasmEpilogContext(SMLoc Loc);
+ bool ensureMasmPrologContext(SMLoc Loc);
unsigned checkTargetMatchPredicate(MCInst &Inst) override;
@@ -4855,42 +4855,42 @@ bool X86AsmParser::ParseDirective(AsmToken DirectiveID) {
else if (Parser.isParsingMasm()) {
// MASM prolog directives.
if (IDVal.equals_insensitive(".pushreg")) {
- return ensureMasmPrologContext(DirectiveID.getLoc(), IDVal) ||
+ return ensureMasmPrologContext(DirectiveID.getLoc()) ||
parseDirectiveSEHPushReg(DirectiveID.getLoc());
} else if (IDVal.equals_insensitive(".push2reg")) {
- return ensureMasmPrologContext(DirectiveID.getLoc(), IDVal) ||
+ return ensureMasmPrologContext(DirectiveID.getLoc()) ||
parseDirectiveSEHPush2Regs(DirectiveID.getLoc());
} else if (IDVal.equals_insensitive(".setframe")) {
- return ensureMasmPrologContext(DirectiveID.getLoc(), IDVal) ||
+ return ensureMasmPrologContext(DirectiveID.getLoc()) ||
parseDirectiveSEHSetFrame(DirectiveID.getLoc());
} else if (IDVal.equals_insensitive(".savereg")) {
- return ensureMasmPrologContext(DirectiveID.getLoc(), IDVal) ||
+ return ensureMasmPrologContext(DirectiveID.getLoc()) ||
parseDirectiveSEHSaveReg(DirectiveID.getLoc());
} else if (IDVal.equals_insensitive(".savexmm128")) {
- return ensureMasmPrologContext(DirectiveID.getLoc(), IDVal) ||
+ return ensureMasmPrologContext(DirectiveID.getLoc()) ||
parseDirectiveSEHSaveXMM(DirectiveID.getLoc());
} else if (IDVal.equals_insensitive(".pushframe")) {
- return ensureMasmPrologContext(DirectiveID.getLoc(), IDVal) ||
+ return ensureMasmPrologContext(DirectiveID.getLoc()) ||
parseDirectiveSEHPushFrame(DirectiveID.getLoc());
}
// MASM epilog directives
if (IDVal.equals_insensitive(".popreg")) {
- return ensureMasmEpilogContext(DirectiveID.getLoc(), IDVal) ||
+ return ensureMasmEpilogContext(DirectiveID.getLoc()) ||
parseDirectiveSEHPushReg(DirectiveID.getLoc());
} else if (IDVal.equals_insensitive(".pop2reg")) {
// .pop2reg args are in the order they are popped, so reverse them to get
// the order they were pushed.
- return ensureMasmEpilogContext(DirectiveID.getLoc(), IDVal) ||
+ return ensureMasmEpilogContext(DirectiveID.getLoc()) ||
parseDirectiveSEHPush2Regs(DirectiveID.getLoc(),
/*SwapRegs=*/true);
} else if (IDVal.equals_insensitive(".unsetframe")) {
- return ensureMasmEpilogContext(DirectiveID.getLoc(), IDVal) ||
+ return ensureMasmEpilogContext(DirectiveID.getLoc()) ||
parseDirectiveSEHSetFrame(DirectiveID.getLoc());
} else if (IDVal.equals_insensitive(".restorereg")) {
- return ensureMasmEpilogContext(DirectiveID.getLoc(), IDVal) ||
+ return ensureMasmEpilogContext(DirectiveID.getLoc()) ||
parseDirectiveSEHSaveReg(DirectiveID.getLoc());
} else if (IDVal.equals_insensitive(".restorexmm128")) {
- return ensureMasmEpilogContext(DirectiveID.getLoc(), IDVal) ||
+ return ensureMasmEpilogContext(DirectiveID.getLoc()) ||
parseDirectiveSEHSaveXMM(DirectiveID.getLoc());
}
}
@@ -5202,14 +5202,14 @@ bool X86AsmParser::parseDirectiveSEHSaveXMM(SMLoc Loc) {
return false;
}
-bool X86AsmParser::ensureMasmPrologContext(SMLoc Loc, StringRef Directive) {
+bool X86AsmParser::ensureMasmPrologContext(SMLoc Loc) {
if (getStreamer().isWinCFIPrologEnded()) {
return Error(Loc, "prolog directive must be used inside a prolog");
}
return false;
}
-bool X86AsmParser::ensureMasmEpilogContext(SMLoc Loc, StringRef Directive) {
+bool X86AsmParser::ensureMasmEpilogContext(SMLoc Loc) {
if (!getStreamer().isInEpilogCFI()) {
return Error(Loc, "epilog directive must be used inside an epilog");
}
diff --git a/llvm/test/tools/llvm-ml/proc_frame_v3.asm b/llvm/test/tools/llvm-ml/proc_frame_v3.asm
index d3e2112455e89..367e08a8b1030 100644
--- a/llvm/test/tools/llvm-ml/proc_frame_v3.asm
+++ b/llvm/test/tools/llvm-ml/proc_frame_v3.asm
@@ -17,7 +17,7 @@ t1 PROC FRAME
.beginepilog
.freestack 32
add rsp, 32
- .pop2reg r12, r13
+ .pop2reg r13, r12
pop2 r13, r12
.endepilog
ret
@@ -35,7 +35,7 @@ t1 ENDP
; CHECK: .seh_startepilogue
; CHECK: .seh_stackalloc 32
; CHECK: add rsp, 32
-; CHECK: .seh_push2regs r13, r12
+; CHECK: .seh_push2regs r12, r13
; CHECK: pop2 r13, r12
; CHECK: .seh_endepilogue
; CHECK: ret
More information about the llvm-commits
mailing list