[llvm] [AArch64] Implement assembler support for new SVE SEH unwind opcodes. (PR #137895)
Eli Friedman via llvm-commits
llvm-commits at lists.llvm.org
Tue Apr 29 17:57:22 PDT 2025
https://github.com/efriedma-quic updated https://github.com/llvm/llvm-project/pull/137895
>From b244f020ff4da0e11cbcf81302688e3c1c681af6 Mon Sep 17 00:00:00 2001
From: Eli Friedman <efriedma at quicinc.com>
Date: Thu, 20 Mar 2025 17:56:17 -0700
Subject: [PATCH] [AArch64] Implement assembler support for new SVE SEH unwind
opcodes.
In order to support the AArch64 ABI, Microsoft has extended the unwinder
to support additional opcodes. (Updated documentation at
https://learn.microsoft.com/en-us/cpp/build/arm64-exception-handling .)
First in a series of patches to support SVE on Windows.
---
llvm/include/llvm/Support/Win64EH.h | 3 +
llvm/lib/MC/MCWin64EH.cpp | 39 +++++++++++++
.../AArch64/AsmParser/AArch64AsmParser.cpp | 57 +++++++++++++++++++
.../MCTargetDesc/AArch64ELFStreamer.cpp | 9 +++
.../MCTargetDesc/AArch64TargetStreamer.h | 6 ++
.../MCTargetDesc/AArch64WinCOFFStreamer.cpp | 14 +++++
llvm/test/MC/AArch64/seh.s | 20 +++++--
llvm/tools/llvm-readobj/ARMWinEHPrinter.cpp | 55 +++++++++++++++++-
llvm/tools/llvm-readobj/ARMWinEHPrinter.h | 8 +++
9 files changed, 205 insertions(+), 6 deletions(-)
diff --git a/llvm/include/llvm/Support/Win64EH.h b/llvm/include/llvm/Support/Win64EH.h
index 8a9be15373917..ec3413b31ee4a 100644
--- a/llvm/include/llvm/Support/Win64EH.h
+++ b/llvm/include/llvm/Support/Win64EH.h
@@ -75,6 +75,9 @@ enum UnwindOpcodes {
UOP_SaveAnyRegDPX,
UOP_SaveAnyRegQX,
UOP_SaveAnyRegQPX,
+ UOP_AllocZ,
+ UOP_SaveZReg,
+ UOP_SavePReg,
// The following set of unwind opcodes is for ARM. They are documented at
// https://docs.microsoft.com/en-us/cpp/build/arm-exception-handling
diff --git a/llvm/lib/MC/MCWin64EH.cpp b/llvm/lib/MC/MCWin64EH.cpp
index bd5cf354659b6..7021c3e31789b 100644
--- a/llvm/lib/MC/MCWin64EH.cpp
+++ b/llvm/lib/MC/MCWin64EH.cpp
@@ -421,6 +421,9 @@ static uint32_t ARM64CountOfUnwindCodes(ArrayRef<WinEH::Instruction> Insns) {
case Win64EH::UOP_PACSignLR:
Count += 1;
break;
+ case Win64EH::UOP_AllocZ:
+ Count += 2;
+ break;
case Win64EH::UOP_SaveAnyRegI:
case Win64EH::UOP_SaveAnyRegIP:
case Win64EH::UOP_SaveAnyRegD:
@@ -433,6 +436,8 @@ static uint32_t ARM64CountOfUnwindCodes(ArrayRef<WinEH::Instruction> Insns) {
case Win64EH::UOP_SaveAnyRegDPX:
case Win64EH::UOP_SaveAnyRegQX:
case Win64EH::UOP_SaveAnyRegQPX:
+ case Win64EH::UOP_SaveZReg:
+ case Win64EH::UOP_SavePReg:
Count += 3;
break;
}
@@ -640,6 +645,37 @@ static void ARM64EmitUnwindCode(MCStreamer &streamer,
streamer.emitInt8(b);
break;
}
+ case Win64EH::UOP_AllocZ: {
+ b = 0xDF;
+ streamer.emitInt8(b);
+ b = inst.Offset;
+ streamer.emitInt8(b);
+ break;
+ }
+ case Win64EH::UOP_SaveZReg: {
+ assert(inst.Register >= 8 && inst.Register <= 23);
+ assert(inst.Offset < 256);
+ b = 0xE7;
+ streamer.emitInt8(b);
+ reg = inst.Register - 8;
+ b = ((inst.Offset & 0xC0) >> 1) | reg;
+ streamer.emitInt8(b);
+ b = 0xC0 | (inst.Offset & 0x3F);
+ streamer.emitInt8(b);
+ break;
+ }
+ case Win64EH::UOP_SavePReg: {
+ assert(inst.Register >= 4 && inst.Register <= 15);
+ assert(inst.Offset < 256);
+ b = 0xE7;
+ streamer.emitInt8(b);
+ reg = inst.Register - 4;
+ b = ((inst.Offset & 0xC0) >> 1) | 0x10 | reg;
+ streamer.emitInt8(b);
+ b = 0xC0 | (inst.Offset & 0x3F);
+ streamer.emitInt8(b);
+ break;
+ }
}
}
@@ -1004,6 +1040,9 @@ static bool tryARM64PackedUnwind(WinEH::FrameInfo *info, uint32_t FuncLength,
case Win64EH::UOP_SaveAnyRegDPX:
case Win64EH::UOP_SaveAnyRegQX:
case Win64EH::UOP_SaveAnyRegQPX:
+ case Win64EH::UOP_AllocZ:
+ case Win64EH::UOP_SaveZReg:
+ case Win64EH::UOP_SavePReg:
// These are never canonical; they don't show up with the usual Arm64
// calling convention.
return false;
diff --git a/llvm/lib/Target/AArch64/AsmParser/AArch64AsmParser.cpp b/llvm/lib/Target/AArch64/AsmParser/AArch64AsmParser.cpp
index e178c5027571a..bcd5fde3b8e27 100644
--- a/llvm/lib/Target/AArch64/AsmParser/AArch64AsmParser.cpp
+++ b/llvm/lib/Target/AArch64/AsmParser/AArch64AsmParser.cpp
@@ -230,6 +230,9 @@ class AArch64AsmParser : public MCTargetAsmParser {
bool parseDirectiveSEHClearUnwoundToCall(SMLoc L);
bool parseDirectiveSEHPACSignLR(SMLoc L);
bool parseDirectiveSEHSaveAnyReg(SMLoc L, bool Paired, bool Writeback);
+ bool parseDirectiveSEHAllocZ(SMLoc L);
+ bool parseDirectiveSEHSaveZReg(SMLoc L);
+ bool parseDirectiveSEHSavePReg(SMLoc L);
bool parseDirectiveAeabiSubSectionHeader(SMLoc L);
bool parseDirectiveAeabiAArch64Attr(SMLoc L);
@@ -7111,6 +7114,12 @@ bool AArch64AsmParser::ParseDirective(AsmToken DirectiveID) {
parseDirectiveSEHSaveAnyReg(Loc, false, true);
else if (IDVal == ".seh_save_any_reg_px")
parseDirectiveSEHSaveAnyReg(Loc, true, true);
+ else if (IDVal == ".seh_allocz")
+ parseDirectiveSEHAllocZ(Loc);
+ else if (IDVal == ".seh_save_zreg")
+ parseDirectiveSEHSaveZReg(Loc);
+ else if (IDVal == ".seh_save_preg")
+ parseDirectiveSEHSavePReg(Loc);
else
return true;
} else if (IsELF) {
@@ -7856,6 +7865,54 @@ bool AArch64AsmParser::parseDirectiveSEHSaveAnyReg(SMLoc L, bool Paired,
return false;
}
+/// parseDirectiveAllocZ
+/// ::= .seh_allocz
+bool AArch64AsmParser::parseDirectiveSEHAllocZ(SMLoc L) {
+ int64_t Offset;
+ if (parseImmExpr(Offset))
+ return true;
+ getTargetStreamer().emitARM64WinCFIAllocZ(Offset);
+ return false;
+}
+
+/// parseDirectiveSEHSaveZReg
+/// ::= .seh_save_zreg
+bool AArch64AsmParser::parseDirectiveSEHSaveZReg(SMLoc L) {
+ MCRegister RegNum;
+ StringRef Kind;
+ int64_t Offset;
+ ParseStatus Res =
+ tryParseVectorRegister(RegNum, Kind, RegKind::SVEDataVector);
+ if (!Res.isSuccess())
+ return true;
+ if (check(RegNum < AArch64::Z8 || RegNum > AArch64::Z23, L,
+ "expected register in range z8 to z23"))
+ return true;
+ if (parseComma() || parseImmExpr(Offset))
+ return true;
+ getTargetStreamer().emitARM64WinCFISaveZReg(RegNum - AArch64::Z0, Offset);
+ return false;
+}
+
+/// parseDirectiveSEHSavePReg
+/// ::= .seh_save_preg
+bool AArch64AsmParser::parseDirectiveSEHSavePReg(SMLoc L) {
+ MCRegister RegNum;
+ StringRef Kind;
+ int64_t Offset;
+ ParseStatus Res =
+ tryParseVectorRegister(RegNum, Kind, RegKind::SVEPredicateVector);
+ if (!Res.isSuccess())
+ return true;
+ if (check(RegNum < AArch64::P4 || RegNum > AArch64::P15, L,
+ "expected register in range p4 to p15"))
+ return true;
+ if (parseComma() || parseImmExpr(Offset))
+ return true;
+ getTargetStreamer().emitARM64WinCFISavePReg(RegNum - AArch64::P0, Offset);
+ return false;
+}
+
bool AArch64AsmParser::parseDirectiveAeabiSubSectionHeader(SMLoc L) {
// Expecting 3 AsmToken::Identifier after '.aeabi_subsection', a name and 2
// parameters, e.g.: .aeabi_subsection (1)aeabi_feature_and_bits, (2)optional,
diff --git a/llvm/lib/Target/AArch64/MCTargetDesc/AArch64ELFStreamer.cpp b/llvm/lib/Target/AArch64/MCTargetDesc/AArch64ELFStreamer.cpp
index b12a12436db81..cc607e863d190 100644
--- a/llvm/lib/Target/AArch64/MCTargetDesc/AArch64ELFStreamer.cpp
+++ b/llvm/lib/Target/AArch64/MCTargetDesc/AArch64ELFStreamer.cpp
@@ -150,6 +150,15 @@ class AArch64TargetAsmStreamer : public AArch64TargetStreamer {
void emitARM64WinCFISaveAnyRegQPX(unsigned Reg, int Offset) override {
OS << "\t.seh_save_any_reg_px\tq" << Reg << ", " << Offset << "\n";
}
+ void emitARM64WinCFIAllocZ(int Offset) override {
+ OS << "\t.seh_allocz\t" << Offset << "\n";
+ }
+ void emitARM64WinCFISaveZReg(unsigned Reg, int Offset) override {
+ OS << "\t.seh_save_zreg\tz" << Reg << ", " << Offset << "\n";
+ }
+ void emitARM64WinCFISavePReg(unsigned Reg, int Offset) override {
+ OS << "\t.seh_save_preg\tp" << Reg << ", " << Offset << "\n";
+ }
void emitAttribute(StringRef VendorName, unsigned Tag, unsigned Value,
std::string String) override {
diff --git a/llvm/lib/Target/AArch64/MCTargetDesc/AArch64TargetStreamer.h b/llvm/lib/Target/AArch64/MCTargetDesc/AArch64TargetStreamer.h
index 43e099f919999..1427427b951a8 100644
--- a/llvm/lib/Target/AArch64/MCTargetDesc/AArch64TargetStreamer.h
+++ b/llvm/lib/Target/AArch64/MCTargetDesc/AArch64TargetStreamer.h
@@ -93,6 +93,9 @@ class AArch64TargetStreamer : public MCTargetStreamer {
virtual void emitARM64WinCFISaveAnyRegDPX(unsigned Reg, int Offset) {}
virtual void emitARM64WinCFISaveAnyRegQX(unsigned Reg, int Offset) {}
virtual void emitARM64WinCFISaveAnyRegQPX(unsigned Reg, int Offset) {}
+ virtual void emitARM64WinCFIAllocZ(int Offset) {}
+ virtual void emitARM64WinCFISaveZReg(unsigned Reg, int Offset) {}
+ virtual void emitARM64WinCFISavePReg(unsigned Reg, int Offset) {}
/// Build attributes implementation
virtual void
@@ -182,6 +185,9 @@ class AArch64TargetWinCOFFStreamer : public llvm::AArch64TargetStreamer {
void emitARM64WinCFISaveAnyRegDPX(unsigned Reg, int Offset) override;
void emitARM64WinCFISaveAnyRegQX(unsigned Reg, int Offset) override;
void emitARM64WinCFISaveAnyRegQPX(unsigned Reg, int Offset) override;
+ void emitARM64WinCFIAllocZ(int Offset) override;
+ void emitARM64WinCFISaveZReg(unsigned Reg, int Offset) override;
+ void emitARM64WinCFISavePReg(unsigned Reg, int Offset) override;
private:
void emitARM64WinUnwindCode(unsigned UnwindCode, int Reg, int Offset);
diff --git a/llvm/lib/Target/AArch64/MCTargetDesc/AArch64WinCOFFStreamer.cpp b/llvm/lib/Target/AArch64/MCTargetDesc/AArch64WinCOFFStreamer.cpp
index fb8a3e1215d90..c03bc386ae349 100644
--- a/llvm/lib/Target/AArch64/MCTargetDesc/AArch64WinCOFFStreamer.cpp
+++ b/llvm/lib/Target/AArch64/MCTargetDesc/AArch64WinCOFFStreamer.cpp
@@ -284,6 +284,20 @@ void AArch64TargetWinCOFFStreamer::emitARM64WinCFISaveAnyRegQPX(unsigned Reg,
emitARM64WinUnwindCode(Win64EH::UOP_SaveAnyRegQPX, Reg, Offset);
}
+void AArch64TargetWinCOFFStreamer::emitARM64WinCFIAllocZ(int Offset) {
+ emitARM64WinUnwindCode(Win64EH::UOP_AllocZ, 0, Offset);
+}
+
+void AArch64TargetWinCOFFStreamer::emitARM64WinCFISaveZReg(unsigned Reg,
+ int Offset) {
+ emitARM64WinUnwindCode(Win64EH::UOP_SaveZReg, Reg, Offset);
+}
+
+void AArch64TargetWinCOFFStreamer::emitARM64WinCFISavePReg(unsigned Reg,
+ int Offset) {
+ emitARM64WinUnwindCode(Win64EH::UOP_SavePReg, Reg, Offset);
+}
+
MCStreamer *
llvm::createAArch64WinCOFFStreamer(MCContext &Context,
std::unique_ptr<MCAsmBackend> &&MAB,
diff --git a/llvm/test/MC/AArch64/seh.s b/llvm/test/MC/AArch64/seh.s
index 5ad4fba2bf6b4..9321d82df567a 100644
--- a/llvm/test/MC/AArch64/seh.s
+++ b/llvm/test/MC/AArch64/seh.s
@@ -20,7 +20,7 @@
// CHECK-NEXT: }
// CHECK: Section {
// CHECK: Name: .xdata
-// CHECK: RawDataSize: 92
+// CHECK: RawDataSize: 100
// CHECK: RelocationCount: 1
// CHECK: Characteristics [
// CHECK-NEXT: ALIGN_4BYTES
@@ -41,7 +41,7 @@
// CHECK-NEXT: Relocations [
// CHECK-NEXT: Section (4) .xdata {
-// CHECK-NEXT: 0x50 IMAGE_REL_ARM64_ADDR32NB __C_specific_handler
+// CHECK-NEXT: 0x58 IMAGE_REL_ARM64_ADDR32NB __C_specific_handler
// CHECK-NEXT: }
// CHECK-NEXT: Section (5) .pdata {
// CHECK-NEXT: 0x0 IMAGE_REL_ARM64_ADDR32NB .text
@@ -54,8 +54,11 @@
// CHECK-NEXT: Function: func
// CHECK-NEXT: ExceptionRecord: .xdata
// CHECK-NEXT: ExceptionData {
-// CHECK-NEXT: FunctionLength: 156
+// CHECK-NEXT: FunctionLength: 172
// CHECK: Prologue [
+// CHECK-NEXT: 0xe712c3 ; str p6, [sp, #3, mul vl]
+// CHECK-NEXT: 0xe703c5 ; str z11, [sp, #5, mul vl]
+// CHECK-NEXT: 0xdf05 ; addvl sp, #-5
// CHECK-NEXT: 0xe76983 ; stp q9, q10, [sp, #-64]!
// CHECK-NEXT: 0xe73d83 ; str q29, [sp, #-64]!
// CHECK-NEXT: 0xe76243 ; stp d2, d3, [sp, #-64]!
@@ -96,8 +99,8 @@
// CHECK-NEXT: ]
// CHECK-NEXT: EpilogueScopes [
// CHECK-NEXT: EpilogueScope {
-// CHECK-NEXT: StartOffset: 37
-// CHECK-NEXT: EpilogueStartIndex: 69
+// CHECK-NEXT: StartOffset: 41
+// CHECK-NEXT: EpilogueStartIndex: 77
// CHECK-NEXT: Opcodes [
// CHECK-NEXT: 0x01 ; add sp, #16
// CHECK-NEXT: 0xe4 ; end
@@ -193,6 +196,13 @@ func:
.seh_save_any_reg_x q29, 64
nop
.seh_save_any_reg_px q9, 64
+ nop
+ .seh_allocz 5
+ nop
+ .seh_save_zreg z11, 5
+ nop
+ .seh_save_preg p6, 3
+ nop
.seh_endprologue
nop
.seh_startepilogue
diff --git a/llvm/tools/llvm-readobj/ARMWinEHPrinter.cpp b/llvm/tools/llvm-readobj/ARMWinEHPrinter.cpp
index 08baa037eab27..3f935231232d7 100644
--- a/llvm/tools/llvm-readobj/ARMWinEHPrinter.cpp
+++ b/llvm/tools/llvm-readobj/ARMWinEHPrinter.cpp
@@ -160,6 +160,7 @@ const Decoder::RingEntry Decoder::Ring64[] = {
{0xfe, 0xda, 2, &Decoder::opcode_save_fregp_x},
{0xfe, 0xdc, 2, &Decoder::opcode_save_freg},
{0xff, 0xde, 2, &Decoder::opcode_save_freg_x},
+ {0xff, 0xdf, 2, &Decoder::opcode_alloc_z},
{0xff, 0xe0, 4, &Decoder::opcode_alloc_l},
{0xff, 0xe1, 1, &Decoder::opcode_setfp},
{0xff, 0xe2, 2, &Decoder::opcode_addfp},
@@ -167,7 +168,7 @@ const Decoder::RingEntry Decoder::Ring64[] = {
{0xff, 0xe4, 1, &Decoder::opcode_end},
{0xff, 0xe5, 1, &Decoder::opcode_end_c},
{0xff, 0xe6, 1, &Decoder::opcode_save_next},
- {0xff, 0xe7, 3, &Decoder::opcode_save_any_reg},
+ {0xff, 0xe7, 3, &Decoder::opcode_e7},
{0xff, 0xe8, 1, &Decoder::opcode_trap_frame},
{0xff, 0xe9, 1, &Decoder::opcode_machine_frame},
{0xff, 0xea, 1, &Decoder::opcode_context},
@@ -805,6 +806,16 @@ bool Decoder::opcode_save_freg_x(const uint8_t *OC, unsigned &Offset,
return false;
}
+bool Decoder::opcode_alloc_z(const uint8_t *OC, unsigned &Offset,
+ unsigned Length, bool Prologue) {
+ unsigned Off = OC[Offset + 1];
+ SW.startLine() << format("0x%02x%02x ; addvl sp, #%d\n",
+ OC[Offset], OC[Offset + 1],
+ Prologue ? -(int)Off : (int)Off);
+ Offset += 2;
+ return false;
+}
+
bool Decoder::opcode_alloc_l(const uint8_t *OC, unsigned &Offset,
unsigned Length, bool Prologue) {
unsigned Off =
@@ -871,6 +882,24 @@ bool Decoder::opcode_save_next(const uint8_t *OC, unsigned &Offset,
return false;
}
+bool Decoder::opcode_e7(const uint8_t *OC, unsigned &Offset, unsigned Length,
+ bool Prologue) {
+ // The e7 opcode has unusual decoding rules; write out the logic.
+ if ((OC[Offset + 1] & 0x80) == 0x80) {
+ SW.getOStream() << "reserved encoding\n";
+ Offset += 3;
+ return false;
+ }
+
+ if ((OC[Offset + 2] & 0xC0) == 0xC0) {
+ if ((OC[Offset + 1] & 0x10) == 0)
+ return opcode_save_zreg(OC, Offset, Length, Prologue);
+ return opcode_save_preg(OC, Offset, Length, Prologue);
+ }
+
+ return opcode_save_any_reg(OC, Offset, Length, Prologue);
+}
+
bool Decoder::opcode_save_any_reg(const uint8_t *OC, unsigned &Offset,
unsigned Length, bool Prologue) {
// Whether the instruction has writeback
@@ -948,6 +977,30 @@ bool Decoder::opcode_save_any_reg(const uint8_t *OC, unsigned &Offset,
return false;
}
+bool Decoder::opcode_save_zreg(const uint8_t *OC, unsigned &Offset,
+ unsigned Length, bool Prologue) {
+ uint32_t Reg = (OC[Offset + 1] & 0x0F) + 8;
+ uint32_t Off = ((OC[Offset + 1] & 0x60) << 1) | (OC[Offset + 2] & 0x3F);
+ SW.startLine() << format(
+ "0x%02x%02x%02x ; %s z%u, [sp, #%u, mul vl]\n", OC[Offset],
+ OC[Offset + 1], OC[Offset + 2],
+ static_cast<const char *>(Prologue ? "str" : "ldr"), Reg, Off);
+ Offset += 3;
+ return false;
+}
+
+bool Decoder::opcode_save_preg(const uint8_t *OC, unsigned &Offset,
+ unsigned Length, bool Prologue) {
+ uint32_t Reg = (OC[Offset + 1] & 0x0F) + 4;
+ uint32_t Off = ((OC[Offset + 1] & 0x60) << 1) | (OC[Offset + 2] & 0x3F);
+ SW.startLine() << format(
+ "0x%02x%02x%02x ; %s p%u, [sp, #%u, mul vl]\n", OC[Offset],
+ OC[Offset + 1], OC[Offset + 2],
+ static_cast<const char *>(Prologue ? "str" : "ldr"), Reg, Off);
+ Offset += 3;
+ return false;
+}
+
bool Decoder::opcode_trap_frame(const uint8_t *OC, unsigned &Offset,
unsigned Length, bool Prologue) {
SW.startLine() << format("0x%02x ; trap frame\n", OC[Offset]);
diff --git a/llvm/tools/llvm-readobj/ARMWinEHPrinter.h b/llvm/tools/llvm-readobj/ARMWinEHPrinter.h
index fa5b31dd87b4b..b412c4a8ae231 100644
--- a/llvm/tools/llvm-readobj/ARMWinEHPrinter.h
+++ b/llvm/tools/llvm-readobj/ARMWinEHPrinter.h
@@ -107,6 +107,8 @@ class Decoder {
unsigned Length, bool Prologue);
bool opcode_save_freg_x(const uint8_t *Opcodes, unsigned &Offset,
unsigned Length, bool Prologue);
+ bool opcode_alloc_z(const uint8_t *Opcodes, unsigned &Offset, unsigned Length,
+ bool Prologue);
bool opcode_alloc_l(const uint8_t *Opcodes, unsigned &Offset, unsigned Length,
bool Prologue);
bool opcode_setfp(const uint8_t *Opcodes, unsigned &Offset, unsigned Length,
@@ -121,6 +123,12 @@ class Decoder {
bool Prologue);
bool opcode_save_next(const uint8_t *Opcodes, unsigned &Offset,
unsigned Length, bool Prologue);
+ bool opcode_e7(const uint8_t *Opcodes, unsigned &Offset, unsigned Length,
+ bool Prologue);
+ bool opcode_save_zreg(const uint8_t *Opcodes, unsigned &Offset,
+ unsigned Length, bool Prologue);
+ bool opcode_save_preg(const uint8_t *Opcodes, unsigned &Offset,
+ unsigned Length, bool Prologue);
bool opcode_save_any_reg(const uint8_t *Opcodes, unsigned &Offset,
unsigned Length, bool Prologue);
bool opcode_trap_frame(const uint8_t *Opcodes, unsigned &Offset,
More information about the llvm-commits
mailing list