Skip to content

Commit 312d6b4

Browse files
[AArch64] Implement assembler support for new SVE SEH unwind opcodes. (#137895)
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.
1 parent 9b4f747 commit 312d6b4

File tree

10 files changed

+209
-8
lines changed

10 files changed

+209
-8
lines changed

llvm/include/llvm/Support/Win64EH.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,9 @@ enum UnwindOpcodes {
7575
UOP_SaveAnyRegDPX,
7676
UOP_SaveAnyRegQX,
7777
UOP_SaveAnyRegQPX,
78+
UOP_AllocZ,
79+
UOP_SaveZReg,
80+
UOP_SavePReg,
7881

7982
// The following set of unwind opcodes is for ARM. They are documented at
8083
// https://docs.microsoft.com/en-us/cpp/build/arm-exception-handling

llvm/lib/MC/MCWin64EH.cpp

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -421,6 +421,9 @@ static uint32_t ARM64CountOfUnwindCodes(ArrayRef<WinEH::Instruction> Insns) {
421421
case Win64EH::UOP_PACSignLR:
422422
Count += 1;
423423
break;
424+
case Win64EH::UOP_AllocZ:
425+
Count += 2;
426+
break;
424427
case Win64EH::UOP_SaveAnyRegI:
425428
case Win64EH::UOP_SaveAnyRegIP:
426429
case Win64EH::UOP_SaveAnyRegD:
@@ -433,6 +436,8 @@ static uint32_t ARM64CountOfUnwindCodes(ArrayRef<WinEH::Instruction> Insns) {
433436
case Win64EH::UOP_SaveAnyRegDPX:
434437
case Win64EH::UOP_SaveAnyRegQX:
435438
case Win64EH::UOP_SaveAnyRegQPX:
439+
case Win64EH::UOP_SaveZReg:
440+
case Win64EH::UOP_SavePReg:
436441
Count += 3;
437442
break;
438443
}
@@ -640,6 +645,37 @@ static void ARM64EmitUnwindCode(MCStreamer &streamer,
640645
streamer.emitInt8(b);
641646
break;
642647
}
648+
case Win64EH::UOP_AllocZ: {
649+
b = 0xDF;
650+
streamer.emitInt8(b);
651+
b = inst.Offset;
652+
streamer.emitInt8(b);
653+
break;
654+
}
655+
case Win64EH::UOP_SaveZReg: {
656+
assert(inst.Register >= 8 && inst.Register <= 23);
657+
assert(inst.Offset < 256);
658+
b = 0xE7;
659+
streamer.emitInt8(b);
660+
reg = inst.Register - 8;
661+
b = ((inst.Offset & 0xC0) >> 1) | reg;
662+
streamer.emitInt8(b);
663+
b = 0xC0 | (inst.Offset & 0x3F);
664+
streamer.emitInt8(b);
665+
break;
666+
}
667+
case Win64EH::UOP_SavePReg: {
668+
assert(inst.Register >= 4 && inst.Register <= 15);
669+
assert(inst.Offset < 256);
670+
b = 0xE7;
671+
streamer.emitInt8(b);
672+
reg = inst.Register;
673+
b = ((inst.Offset & 0xC0) >> 1) | 0x10 | reg;
674+
streamer.emitInt8(b);
675+
b = 0xC0 | (inst.Offset & 0x3F);
676+
streamer.emitInt8(b);
677+
break;
678+
}
643679
}
644680
}
645681

@@ -1016,6 +1052,11 @@ static bool tryARM64PackedUnwind(WinEH::FrameInfo *info, uint32_t FuncLength,
10161052
// "add x29, sp, #N" doesn't show up in the canonical pattern (except for
10171053
// N=0, which is UOP_SetFP).
10181054
return false;
1055+
case Win64EH::UOP_AllocZ:
1056+
case Win64EH::UOP_SaveZReg:
1057+
case Win64EH::UOP_SavePReg:
1058+
// Canonical prologues don't support spilling SVE registers.
1059+
return false;
10191060
case Win64EH::UOP_TrapFrame:
10201061
case Win64EH::UOP_Context:
10211062
case Win64EH::UOP_ECContext:

llvm/lib/Target/AArch64/AsmParser/AArch64AsmParser.cpp

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,9 @@ class AArch64AsmParser : public MCTargetAsmParser {
230230
bool parseDirectiveSEHClearUnwoundToCall(SMLoc L);
231231
bool parseDirectiveSEHPACSignLR(SMLoc L);
232232
bool parseDirectiveSEHSaveAnyReg(SMLoc L, bool Paired, bool Writeback);
233+
bool parseDirectiveSEHAllocZ(SMLoc L);
234+
bool parseDirectiveSEHSaveZReg(SMLoc L);
235+
bool parseDirectiveSEHSavePReg(SMLoc L);
233236
bool parseDirectiveAeabiSubSectionHeader(SMLoc L);
234237
bool parseDirectiveAeabiAArch64Attr(SMLoc L);
235238

@@ -7111,6 +7114,12 @@ bool AArch64AsmParser::ParseDirective(AsmToken DirectiveID) {
71117114
parseDirectiveSEHSaveAnyReg(Loc, false, true);
71127115
else if (IDVal == ".seh_save_any_reg_px")
71137116
parseDirectiveSEHSaveAnyReg(Loc, true, true);
7117+
else if (IDVal == ".seh_allocz")
7118+
parseDirectiveSEHAllocZ(Loc);
7119+
else if (IDVal == ".seh_save_zreg")
7120+
parseDirectiveSEHSaveZReg(Loc);
7121+
else if (IDVal == ".seh_save_preg")
7122+
parseDirectiveSEHSavePReg(Loc);
71147123
else
71157124
return true;
71167125
} else if (IsELF) {
@@ -7856,6 +7865,54 @@ bool AArch64AsmParser::parseDirectiveSEHSaveAnyReg(SMLoc L, bool Paired,
78567865
return false;
78577866
}
78587867

7868+
/// parseDirectiveAllocZ
7869+
/// ::= .seh_allocz
7870+
bool AArch64AsmParser::parseDirectiveSEHAllocZ(SMLoc L) {
7871+
int64_t Offset;
7872+
if (parseImmExpr(Offset))
7873+
return true;
7874+
getTargetStreamer().emitARM64WinCFIAllocZ(Offset);
7875+
return false;
7876+
}
7877+
7878+
/// parseDirectiveSEHSaveZReg
7879+
/// ::= .seh_save_zreg
7880+
bool AArch64AsmParser::parseDirectiveSEHSaveZReg(SMLoc L) {
7881+
MCRegister RegNum;
7882+
StringRef Kind;
7883+
int64_t Offset;
7884+
ParseStatus Res =
7885+
tryParseVectorRegister(RegNum, Kind, RegKind::SVEDataVector);
7886+
if (!Res.isSuccess())
7887+
return true;
7888+
if (check(RegNum < AArch64::Z8 || RegNum > AArch64::Z23, L,
7889+
"expected register in range z8 to z23"))
7890+
return true;
7891+
if (parseComma() || parseImmExpr(Offset))
7892+
return true;
7893+
getTargetStreamer().emitARM64WinCFISaveZReg(RegNum - AArch64::Z0, Offset);
7894+
return false;
7895+
}
7896+
7897+
/// parseDirectiveSEHSavePReg
7898+
/// ::= .seh_save_preg
7899+
bool AArch64AsmParser::parseDirectiveSEHSavePReg(SMLoc L) {
7900+
MCRegister RegNum;
7901+
StringRef Kind;
7902+
int64_t Offset;
7903+
ParseStatus Res =
7904+
tryParseVectorRegister(RegNum, Kind, RegKind::SVEPredicateVector);
7905+
if (!Res.isSuccess())
7906+
return true;
7907+
if (check(RegNum < AArch64::P4 || RegNum > AArch64::P15, L,
7908+
"expected register in range p4 to p15"))
7909+
return true;
7910+
if (parseComma() || parseImmExpr(Offset))
7911+
return true;
7912+
getTargetStreamer().emitARM64WinCFISavePReg(RegNum - AArch64::P0, Offset);
7913+
return false;
7914+
}
7915+
78597916
bool AArch64AsmParser::parseDirectiveAeabiSubSectionHeader(SMLoc L) {
78607917
// Expecting 3 AsmToken::Identifier after '.aeabi_subsection', a name and 2
78617918
// parameters, e.g.: .aeabi_subsection (1)aeabi_feature_and_bits, (2)optional,

llvm/lib/Target/AArch64/MCTargetDesc/AArch64ELFStreamer.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,15 @@ class AArch64TargetAsmStreamer : public AArch64TargetStreamer {
150150
void emitARM64WinCFISaveAnyRegQPX(unsigned Reg, int Offset) override {
151151
OS << "\t.seh_save_any_reg_px\tq" << Reg << ", " << Offset << "\n";
152152
}
153+
void emitARM64WinCFIAllocZ(int Offset) override {
154+
OS << "\t.seh_allocz\t" << Offset << "\n";
155+
}
156+
void emitARM64WinCFISaveZReg(unsigned Reg, int Offset) override {
157+
OS << "\t.seh_save_zreg\tz" << Reg << ", " << Offset << "\n";
158+
}
159+
void emitARM64WinCFISavePReg(unsigned Reg, int Offset) override {
160+
OS << "\t.seh_save_preg\tp" << Reg << ", " << Offset << "\n";
161+
}
153162

154163
void emitAttribute(StringRef VendorName, unsigned Tag, unsigned Value,
155164
std::string String) override {

llvm/lib/Target/AArch64/MCTargetDesc/AArch64TargetStreamer.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,9 @@ class AArch64TargetStreamer : public MCTargetStreamer {
9393
virtual void emitARM64WinCFISaveAnyRegDPX(unsigned Reg, int Offset) {}
9494
virtual void emitARM64WinCFISaveAnyRegQX(unsigned Reg, int Offset) {}
9595
virtual void emitARM64WinCFISaveAnyRegQPX(unsigned Reg, int Offset) {}
96+
virtual void emitARM64WinCFIAllocZ(int Offset) {}
97+
virtual void emitARM64WinCFISaveZReg(unsigned Reg, int Offset) {}
98+
virtual void emitARM64WinCFISavePReg(unsigned Reg, int Offset) {}
9699

97100
/// Build attributes implementation
98101
virtual void
@@ -182,6 +185,9 @@ class AArch64TargetWinCOFFStreamer : public llvm::AArch64TargetStreamer {
182185
void emitARM64WinCFISaveAnyRegDPX(unsigned Reg, int Offset) override;
183186
void emitARM64WinCFISaveAnyRegQX(unsigned Reg, int Offset) override;
184187
void emitARM64WinCFISaveAnyRegQPX(unsigned Reg, int Offset) override;
188+
void emitARM64WinCFIAllocZ(int Offset) override;
189+
void emitARM64WinCFISaveZReg(unsigned Reg, int Offset) override;
190+
void emitARM64WinCFISavePReg(unsigned Reg, int Offset) override;
185191

186192
private:
187193
void emitARM64WinUnwindCode(unsigned UnwindCode, int Reg, int Offset);

llvm/lib/Target/AArch64/MCTargetDesc/AArch64WinCOFFStreamer.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,20 @@ void AArch64TargetWinCOFFStreamer::emitARM64WinCFISaveAnyRegQPX(unsigned Reg,
284284
emitARM64WinUnwindCode(Win64EH::UOP_SaveAnyRegQPX, Reg, Offset);
285285
}
286286

287+
void AArch64TargetWinCOFFStreamer::emitARM64WinCFIAllocZ(int Offset) {
288+
emitARM64WinUnwindCode(Win64EH::UOP_AllocZ, 0, Offset);
289+
}
290+
291+
void AArch64TargetWinCOFFStreamer::emitARM64WinCFISaveZReg(unsigned Reg,
292+
int Offset) {
293+
emitARM64WinUnwindCode(Win64EH::UOP_SaveZReg, Reg, Offset);
294+
}
295+
296+
void AArch64TargetWinCOFFStreamer::emitARM64WinCFISavePReg(unsigned Reg,
297+
int Offset) {
298+
emitARM64WinUnwindCode(Win64EH::UOP_SavePReg, Reg, Offset);
299+
}
300+
287301
MCStreamer *
288302
llvm::createAArch64WinCOFFStreamer(MCContext &Context,
289303
std::unique_ptr<MCAsmBackend> &&MAB,

llvm/test/MC/AArch64/seh.s

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
// CHECK-NEXT: }
2121
// CHECK: Section {
2222
// CHECK: Name: .xdata
23-
// CHECK: RawDataSize: 92
23+
// CHECK: RawDataSize: 100
2424
// CHECK: RelocationCount: 1
2525
// CHECK: Characteristics [
2626
// CHECK-NEXT: ALIGN_4BYTES
@@ -41,7 +41,7 @@
4141

4242
// CHECK-NEXT: Relocations [
4343
// CHECK-NEXT: Section (4) .xdata {
44-
// CHECK-NEXT: 0x50 IMAGE_REL_ARM64_ADDR32NB __C_specific_handler
44+
// CHECK-NEXT: 0x58 IMAGE_REL_ARM64_ADDR32NB __C_specific_handler
4545
// CHECK-NEXT: }
4646
// CHECK-NEXT: Section (5) .pdata {
4747
// CHECK-NEXT: 0x0 IMAGE_REL_ARM64_ADDR32NB .text
@@ -54,8 +54,11 @@
5454
// CHECK-NEXT: Function: func
5555
// CHECK-NEXT: ExceptionRecord: .xdata
5656
// CHECK-NEXT: ExceptionData {
57-
// CHECK-NEXT: FunctionLength: 156
57+
// CHECK-NEXT: FunctionLength: 172
5858
// CHECK: Prologue [
59+
// CHECK-NEXT: 0xe716c3 ; str p6, [sp, #3, mul vl]
60+
// CHECK-NEXT: 0xe703c5 ; str z11, [sp, #5, mul vl]
61+
// CHECK-NEXT: 0xdf05 ; addvl sp, #-5
5962
// CHECK-NEXT: 0xe76983 ; stp q9, q10, [sp, #-64]!
6063
// CHECK-NEXT: 0xe73d83 ; str q29, [sp, #-64]!
6164
// CHECK-NEXT: 0xe76243 ; stp d2, d3, [sp, #-64]!
@@ -96,8 +99,8 @@
9699
// CHECK-NEXT: ]
97100
// CHECK-NEXT: EpilogueScopes [
98101
// CHECK-NEXT: EpilogueScope {
99-
// CHECK-NEXT: StartOffset: 37
100-
// CHECK-NEXT: EpilogueStartIndex: 69
102+
// CHECK-NEXT: StartOffset: 41
103+
// CHECK-NEXT: EpilogueStartIndex: 77
101104
// CHECK-NEXT: Opcodes [
102105
// CHECK-NEXT: 0x01 ; add sp, #16
103106
// CHECK-NEXT: 0xe4 ; end
@@ -193,6 +196,13 @@ func:
193196
.seh_save_any_reg_x q29, 64
194197
nop
195198
.seh_save_any_reg_px q9, 64
199+
nop
200+
.seh_allocz 5
201+
nop
202+
.seh_save_zreg z11, 5
203+
nop
204+
.seh_save_preg p6, 3
205+
nop
196206
.seh_endprologue
197207
nop
198208
.seh_startepilogue

llvm/test/tools/llvm-readobj/COFF/arm64-win-error1.s

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
// RUN: | llvm-readobj --unwind - | FileCheck %s
77

88
// CHECK: Prologue [
9-
// CHECK: 0xdf ; Bad opcode!
9+
// CHECK: 0xef ; Bad opcode!
1010
// CHECK: 0xff ; Bad opcode!
1111
// CHECK: 0xd600 ; stp x19, lr, [sp, #0]
1212
// CHECK: 0x01 ; sub sp, #16
@@ -49,6 +49,6 @@
4949
.long 0x10800012
5050
.long 0x8
5151
.long 0xe
52-
.long 0x00d6ffdf
52+
.long 0x00d6ffef
5353
.long 0xe3e3e401
5454

llvm/tools/llvm-readobj/ARMWinEHPrinter.cpp

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,14 +160,15 @@ const Decoder::RingEntry Decoder::Ring64[] = {
160160
{0xfe, 0xda, 2, &Decoder::opcode_save_fregp_x},
161161
{0xfe, 0xdc, 2, &Decoder::opcode_save_freg},
162162
{0xff, 0xde, 2, &Decoder::opcode_save_freg_x},
163+
{0xff, 0xdf, 2, &Decoder::opcode_alloc_z},
163164
{0xff, 0xe0, 4, &Decoder::opcode_alloc_l},
164165
{0xff, 0xe1, 1, &Decoder::opcode_setfp},
165166
{0xff, 0xe2, 2, &Decoder::opcode_addfp},
166167
{0xff, 0xe3, 1, &Decoder::opcode_nop},
167168
{0xff, 0xe4, 1, &Decoder::opcode_end},
168169
{0xff, 0xe5, 1, &Decoder::opcode_end_c},
169170
{0xff, 0xe6, 1, &Decoder::opcode_save_next},
170-
{0xff, 0xe7, 3, &Decoder::opcode_save_any_reg},
171+
{0xff, 0xe7, 3, &Decoder::opcode_e7},
171172
{0xff, 0xe8, 1, &Decoder::opcode_trap_frame},
172173
{0xff, 0xe9, 1, &Decoder::opcode_machine_frame},
173174
{0xff, 0xea, 1, &Decoder::opcode_context},
@@ -805,6 +806,16 @@ bool Decoder::opcode_save_freg_x(const uint8_t *OC, unsigned &Offset,
805806
return false;
806807
}
807808

809+
bool Decoder::opcode_alloc_z(const uint8_t *OC, unsigned &Offset,
810+
unsigned Length, bool Prologue) {
811+
unsigned Off = OC[Offset + 1];
812+
SW.startLine() << format("0x%02x%02x ; addvl sp, #%d\n",
813+
OC[Offset], OC[Offset + 1],
814+
Prologue ? -(int)Off : (int)Off);
815+
Offset += 2;
816+
return false;
817+
}
818+
808819
bool Decoder::opcode_alloc_l(const uint8_t *OC, unsigned &Offset,
809820
unsigned Length, bool Prologue) {
810821
unsigned Off =
@@ -871,6 +882,24 @@ bool Decoder::opcode_save_next(const uint8_t *OC, unsigned &Offset,
871882
return false;
872883
}
873884

885+
bool Decoder::opcode_e7(const uint8_t *OC, unsigned &Offset, unsigned Length,
886+
bool Prologue) {
887+
// The e7 opcode has unusual decoding rules; write out the logic.
888+
if ((OC[Offset + 1] & 0x80) == 0x80) {
889+
SW.getOStream() << "reserved encoding\n";
890+
Offset += 3;
891+
return false;
892+
}
893+
894+
if ((OC[Offset + 2] & 0xC0) == 0xC0) {
895+
if ((OC[Offset + 1] & 0x10) == 0)
896+
return opcode_save_zreg(OC, Offset, Length, Prologue);
897+
return opcode_save_preg(OC, Offset, Length, Prologue);
898+
}
899+
900+
return opcode_save_any_reg(OC, Offset, Length, Prologue);
901+
}
902+
874903
bool Decoder::opcode_save_any_reg(const uint8_t *OC, unsigned &Offset,
875904
unsigned Length, bool Prologue) {
876905
// Whether the instruction has writeback
@@ -948,6 +977,30 @@ bool Decoder::opcode_save_any_reg(const uint8_t *OC, unsigned &Offset,
948977
return false;
949978
}
950979

980+
bool Decoder::opcode_save_zreg(const uint8_t *OC, unsigned &Offset,
981+
unsigned Length, bool Prologue) {
982+
uint32_t Reg = (OC[Offset + 1] & 0x0F) + 8;
983+
uint32_t Off = ((OC[Offset + 1] & 0x60) << 1) | (OC[Offset + 2] & 0x3F);
984+
SW.startLine() << format(
985+
"0x%02x%02x%02x ; %s z%u, [sp, #%u, mul vl]\n", OC[Offset],
986+
OC[Offset + 1], OC[Offset + 2],
987+
static_cast<const char *>(Prologue ? "str" : "ldr"), Reg, Off);
988+
Offset += 3;
989+
return false;
990+
}
991+
992+
bool Decoder::opcode_save_preg(const uint8_t *OC, unsigned &Offset,
993+
unsigned Length, bool Prologue) {
994+
uint32_t Reg = (OC[Offset + 1] & 0x0F);
995+
uint32_t Off = ((OC[Offset + 1] & 0x60) << 1) | (OC[Offset + 2] & 0x3F);
996+
SW.startLine() << format(
997+
"0x%02x%02x%02x ; %s p%u, [sp, #%u, mul vl]\n", OC[Offset],
998+
OC[Offset + 1], OC[Offset + 2],
999+
static_cast<const char *>(Prologue ? "str" : "ldr"), Reg, Off);
1000+
Offset += 3;
1001+
return false;
1002+
}
1003+
9511004
bool Decoder::opcode_trap_frame(const uint8_t *OC, unsigned &Offset,
9521005
unsigned Length, bool Prologue) {
9531006
SW.startLine() << format("0x%02x ; trap frame\n", OC[Offset]);

llvm/tools/llvm-readobj/ARMWinEHPrinter.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,8 @@ class Decoder {
107107
unsigned Length, bool Prologue);
108108
bool opcode_save_freg_x(const uint8_t *Opcodes, unsigned &Offset,
109109
unsigned Length, bool Prologue);
110+
bool opcode_alloc_z(const uint8_t *Opcodes, unsigned &Offset, unsigned Length,
111+
bool Prologue);
110112
bool opcode_alloc_l(const uint8_t *Opcodes, unsigned &Offset, unsigned Length,
111113
bool Prologue);
112114
bool opcode_setfp(const uint8_t *Opcodes, unsigned &Offset, unsigned Length,
@@ -121,6 +123,12 @@ class Decoder {
121123
bool Prologue);
122124
bool opcode_save_next(const uint8_t *Opcodes, unsigned &Offset,
123125
unsigned Length, bool Prologue);
126+
bool opcode_e7(const uint8_t *Opcodes, unsigned &Offset, unsigned Length,
127+
bool Prologue);
128+
bool opcode_save_zreg(const uint8_t *Opcodes, unsigned &Offset,
129+
unsigned Length, bool Prologue);
130+
bool opcode_save_preg(const uint8_t *Opcodes, unsigned &Offset,
131+
unsigned Length, bool Prologue);
124132
bool opcode_save_any_reg(const uint8_t *Opcodes, unsigned &Offset,
125133
unsigned Length, bool Prologue);
126134
bool opcode_trap_frame(const uint8_t *Opcodes, unsigned &Offset,

0 commit comments

Comments
 (0)