Skip to content

Commit f31150e

Browse files
committed
[win][x64] Various fixes for unwind v2
* `SetFrame` does not count as a stack allocation. * `mov` in the epilog undoes `SetFrame` (but is not required), it does not deallocate a stack allocation. * `mov` in the epilog MUST be before any stack deallocation or register popping. * Do not try to enable unwind v2 for `noreturn` functions as they don't have epilogs. * Improve the errors in `MC` to include the problematic function name.
1 parent d69ccde commit f31150e

File tree

4 files changed

+216
-22
lines changed

4 files changed

+216
-22
lines changed

llvm/lib/MC/MCWin64EH.cpp

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ class MCSection;
2222

2323
/// MCExpr that represents the epilog unwind code in an unwind table.
2424
class MCUnwindV2EpilogTargetExpr final : public MCTargetExpr {
25+
const MCSymbol *Function;
2526
const MCSymbol *FunctionEnd;
2627
const MCSymbol *UnwindV2Start;
2728
const MCSymbol *EpilogEnd;
@@ -31,7 +32,7 @@ class MCUnwindV2EpilogTargetExpr final : public MCTargetExpr {
3132
MCUnwindV2EpilogTargetExpr(const WinEH::FrameInfo &FrameInfo,
3233
const WinEH::FrameInfo::Epilog &Epilog,
3334
uint8_t EpilogSize_)
34-
: FunctionEnd(FrameInfo.FuncletOrFuncEnd),
35+
: Function(FrameInfo.Function), FunctionEnd(FrameInfo.FuncletOrFuncEnd),
3536
UnwindV2Start(Epilog.UnwindV2Start), EpilogEnd(Epilog.End),
3637
EpilogSize(EpilogSize_), Loc(Epilog.Loc) {}
3738

@@ -253,13 +254,15 @@ static void EmitUnwindInfo(MCStreamer &streamer, WinEH::FrameInfo *info) {
253254
OS->getAssembler(), LastEpilog.End, LastEpilog.UnwindV2Start);
254255
if (!MaybeSize) {
255256
context.reportError(LastEpilog.Loc,
256-
"Failed to evaluate epilog size for Unwind v2");
257+
"Failed to evaluate epilog size for Unwind v2 in " +
258+
info->Function->getName());
257259
return;
258260
}
259261
assert(*MaybeSize >= 0);
260262
if (*MaybeSize >= (int64_t)UINT8_MAX) {
261263
context.reportError(LastEpilog.Loc,
262-
"Epilog size is too large for Unwind v2");
264+
"Epilog size is too large for Unwind v2 in " +
265+
info->Function->getName());
263266
return;
264267
}
265268
EpilogSize = *MaybeSize + 1;
@@ -282,7 +285,8 @@ static void EmitUnwindInfo(MCStreamer &streamer, WinEH::FrameInfo *info) {
282285
// Too many epilogs to handle.
283286
if ((size_t)numCodes + numEpilogCodes > UINT8_MAX) {
284287
context.reportError(info->FunctionLoc,
285-
"Too many unwind codes with Unwind v2 enabled");
288+
"Too many unwind codes with Unwind v2 enabled in " +
289+
info->Function->getName());
286290
return;
287291
}
288292

@@ -383,23 +387,25 @@ bool MCUnwindV2EpilogTargetExpr::evaluateAsRelocatableImpl(
383387
auto Offset = GetOptionalAbsDifference(*Asm, FunctionEnd, UnwindV2Start);
384388
if (!Offset) {
385389
Asm->getContext().reportError(
386-
Loc, "Failed to evaluate epilog offset for Unwind v2");
390+
Loc, "Failed to evaluate epilog offset for Unwind v2 in " +
391+
Function->getName());
387392
return false;
388393
}
389394
assert(*Offset > 0);
390395
constexpr uint16_t MaxEpilogOffset = 0x0fff;
391396
if (*Offset > MaxEpilogOffset) {
392-
Asm->getContext().reportError(Loc,
393-
"Epilog offset is too large for Unwind v2");
397+
Asm->getContext().reportError(
398+
Loc,
399+
"Epilog offset is too large for Unwind v2 in " + Function->getName());
394400
return false;
395401
}
396402

397403
// Sanity check that all epilogs are the same size.
398404
auto Size = GetOptionalAbsDifference(*Asm, EpilogEnd, UnwindV2Start);
399405
if (Size != (EpilogSize - 1)) {
400406
Asm->getContext().reportError(
401-
Loc,
402-
"Size of this epilog does not match size of last epilog in function");
407+
Loc, "Size of this epilog does not match size of last epilog in " +
408+
Function->getName());
403409
return false;
404410
}
405411

llvm/lib/Target/X86/X86WinEHUnwindV2.cpp

Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ bool X86WinEHUnwindV2::runOnMachineFunction(MachineFunction &MF) {
105105
// Prolog information.
106106
SmallVector<int64_t> PushedRegs;
107107
bool HasStackAlloc = false;
108+
bool HasSetFrame = false;
108109
unsigned ApproximatePrologCodeCount = 0;
109110

110111
// Requested changes.
@@ -130,15 +131,20 @@ bool X86WinEHUnwindV2::runOnMachineFunction(MachineFunction &MF) {
130131
break;
131132

132133
case X86::SEH_StackAlloc:
133-
case X86::SEH_SetFrame:
134134
if (State != FunctionState::InProlog)
135-
llvm_unreachable("SEH_StackAlloc or SEH_SetFrame outside of prolog");
135+
llvm_unreachable("SEH_StackAlloc outside of prolog");
136136
// Assume a large alloc...
137-
ApproximatePrologCodeCount +=
138-
(MI.getOpcode() == X86::SEH_StackAlloc) ? 3 : 1;
137+
ApproximatePrologCodeCount += 3;
139138
HasStackAlloc = true;
140139
break;
141140

141+
case X86::SEH_SetFrame:
142+
if (State != FunctionState::InProlog)
143+
llvm_unreachable("SEH_SetFrame outside of prolog");
144+
ApproximatePrologCodeCount++;
145+
HasSetFrame = true;
146+
break;
147+
142148
case X86::SEH_SaveReg:
143149
case X86::SEH_SaveXMM:
144150
if (State != FunctionState::InProlog)
@@ -190,8 +196,30 @@ bool X86WinEHUnwindV2::runOnMachineFunction(MachineFunction &MF) {
190196
State = FunctionState::FinishedEpilog;
191197
break;
192198

193-
case X86::LEA64r:
194199
case X86::MOV64rr:
200+
if (State == FunctionState::InEpilog) {
201+
// If the prolog contains a stack allocation, then the first
202+
// instruction in the epilog must be to adjust the stack pointer.
203+
if (!HasSetFrame)
204+
return rejectCurrentFunctionInternalError(
205+
MF, Mode,
206+
"The epilog is setting frame back, but prolog did not set it");
207+
if (PoppedRegCount > 0)
208+
return rejectCurrentFunctionInternalError(
209+
MF, Mode,
210+
"The epilog is setting the frame back after popping "
211+
"registers");
212+
if (HasStackDealloc)
213+
return rejectCurrentFunctionInternalError(
214+
MF, Mode,
215+
"Cannot set the frame back after the stack "
216+
"allocation has been deallocated");
217+
} else if (State == FunctionState::FinishedEpilog)
218+
return rejectCurrentFunctionInternalError(
219+
MF, Mode, "Unexpected mov instruction after the epilog");
220+
break;
221+
222+
case X86::LEA64r:
195223
case X86::ADD64ri32:
196224
if (State == FunctionState::InEpilog) {
197225
// If the prolog contains a stack allocation, then the first
@@ -211,8 +239,7 @@ bool X86WinEHUnwindV2::runOnMachineFunction(MachineFunction &MF) {
211239
HasStackDealloc = true;
212240
} else if (State == FunctionState::FinishedEpilog)
213241
return rejectCurrentFunctionInternalError(
214-
MF, Mode,
215-
"Unexpected lea, mov or add instruction after the epilog");
242+
MF, Mode, "Unexpected lea or add instruction after the epilog");
216243
break;
217244

218245
case X86::POP64r:
@@ -278,11 +305,8 @@ bool X86WinEHUnwindV2::runOnMachineFunction(MachineFunction &MF) {
278305
}
279306
}
280307

281-
if (UnwindV2StartLocations.empty()) {
282-
assert(State == FunctionState::InProlog &&
283-
"If there are no epilogs, then there should be no prolog");
308+
if (UnwindV2StartLocations.empty())
284309
return false;
285-
}
286310

287311
MachineBasicBlock &FirstMBB = MF.front();
288312
// Assume +1 for the "header" UOP_Epilog that contains the epilog size, and

llvm/test/CodeGen/X86/win64-eh-unwindv2-errors.mir

Lines changed: 130 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ body: |
106106
# RUN: -x86-wineh-unwindv2-force-mode=1 | FileCheck %s \
107107
# RUN: --check-prefix=BESTEFFORT
108108
# DEALLOC-AFTER-EPILOG: LLVM ERROR: Windows x64 Unwind v2 is required, but LLVM has generated incompatible code in function 'dealloc_after_epilog':
109-
# DEALLOC-AFTER-EPILOG-SAME: Unexpected lea, mov or add instruction after the epilog
109+
# DEALLOC-AFTER-EPILOG-SAME: Unexpected lea or add instruction after the epilog
110110

111111
--- |
112112
define dso_local void @dealloc_after_epilog() local_unnamed_addr {
@@ -161,6 +161,135 @@ body: |
161161
RET64
162162
...
163163

164+
;--- mov_no_setframe.mir
165+
# RUN: not --crash llc -mtriple=x86_64-pc-windows-msvc -o - \
166+
# RUN: %t/mov_no_setframe.mir -run-pass=x86-wineh-unwindv2 2>&1 | \
167+
# RUN: FileCheck %s --check-prefix=MOV-NO-SETFRAME
168+
# RUN: llc -mtriple=x86_64-pc-windows-msvc -o - %t/mov_no_setframe.mir \
169+
# RUN: -run-pass=x86-wineh-unwindv2 -x86-wineh-unwindv2-force-mode=1 | \
170+
# RUN: FileCheck %s --check-prefix=BESTEFFORT
171+
# MOV-NO-SETFRAME: LLVM ERROR: Windows x64 Unwind v2 is required, but LLVM has generated incompatible code in function 'mov_no_setframe':
172+
# MOV-NO-SETFRAME-SAME: The epilog is setting frame back, but prolog did not set it
173+
174+
--- |
175+
define dso_local void @mov_no_setframe() local_unnamed_addr {
176+
entry:
177+
ret void
178+
}
179+
!llvm.module.flags = !{!0}
180+
!0 = !{i32 1, !"winx64-eh-unwindv2", i32 2}
181+
...
182+
---
183+
name: mov_no_setframe
184+
body: |
185+
bb.0.entry:
186+
frame-setup SEH_EndPrologue
187+
SEH_BeginEpilogue
188+
$rsp = MOV64rr $rbp
189+
SEH_EndEpilogue
190+
RET64
191+
...
192+
193+
;--- mov_after_epilog.mir
194+
# RUN: not --crash llc -mtriple=x86_64-pc-windows-msvc -o - \
195+
# RUN: %t/mov_after_epilog.mir -run-pass=x86-wineh-unwindv2 2>&1 | \
196+
# RUN: FileCheck %s --check-prefix=MOV-AFTER-EPILOG
197+
# RUN: llc -mtriple=x86_64-pc-windows-msvc -o - \
198+
# RUN: %t/mov_after_epilog.mir -run-pass=x86-wineh-unwindv2 \
199+
# RUN: -x86-wineh-unwindv2-force-mode=1 | FileCheck %s \
200+
# RUN: --check-prefix=BESTEFFORT
201+
# MOV-AFTER-EPILOG: LLVM ERROR: Windows x64 Unwind v2 is required, but LLVM has generated incompatible code in function 'mov_after_epilog':
202+
# MOV-AFTER-EPILOG-SAME: Unexpected mov instruction after the epilog
203+
204+
--- |
205+
define dso_local void @mov_after_epilog() local_unnamed_addr {
206+
entry:
207+
ret void
208+
}
209+
!llvm.module.flags = !{!0}
210+
!0 = !{i32 1, !"winx64-eh-unwindv2", i32 2}
211+
...
212+
---
213+
name: mov_after_epilog
214+
body: |
215+
bb.0.entry:
216+
$rbp = MOV64rr $rsp
217+
frame-setup SEH_SetFrame 52, 0
218+
frame-setup SEH_EndPrologue
219+
SEH_BeginEpilogue
220+
SEH_EndEpilogue
221+
$rsp = MOV64rr $rbp
222+
RET64
223+
...
224+
225+
;--- pop_before_mov.mir
226+
# RUN: not --crash llc -mtriple=x86_64-pc-windows-msvc -o - \
227+
# RUN: %t/pop_before_mov.mir -run-pass=x86-wineh-unwindv2 2>&1 | \
228+
# RUN: FileCheck %s --check-prefix=POP-BEFORE-MOV
229+
# RUN: llc -mtriple=x86_64-pc-windows-msvc -o - %t/pop_before_mov.mir \
230+
# RUN: -run-pass=x86-wineh-unwindv2 -x86-wineh-unwindv2-force-mode=1 | \
231+
# RUN: FileCheck %s --check-prefix=BESTEFFORT
232+
# POP-BEFORE-MOV: LLVM ERROR: Windows x64 Unwind v2 is required, but LLVM has generated incompatible code in function 'pop_before_mov':
233+
# POP-BEFORE-MOV-SAME: The epilog is setting the frame back after popping registers
234+
235+
--- |
236+
define dso_local void @pop_before_mov() local_unnamed_addr {
237+
entry:
238+
ret void
239+
}
240+
!llvm.module.flags = !{!0}
241+
!0 = !{i32 1, !"winx64-eh-unwindv2", i32 2}
242+
...
243+
---
244+
name: pop_before_mov
245+
body: |
246+
bb.0.entry:
247+
frame-setup PUSH64r killed $rdi, implicit-def $rsp, implicit $rsp
248+
frame-setup SEH_PushReg 55
249+
$rbp = MOV64rr $rsp
250+
frame-setup SEH_SetFrame 52, 0
251+
frame-setup SEH_EndPrologue
252+
SEH_BeginEpilogue
253+
$rdi = frame-destroy POP64r implicit-def $rsp, implicit $rsp
254+
$rsp = MOV64rr $rbp
255+
SEH_EndEpilogue
256+
RET64
257+
...
258+
259+
;--- mov_after_dealloc.mir
260+
# RUN: not --crash llc -mtriple=x86_64-pc-windows-msvc -o - \
261+
# RUN: %t/mov_after_dealloc.mir -run-pass=x86-wineh-unwindv2 2>&1 | \
262+
# RUN: FileCheck %s --check-prefix=MOV-AFTER-DEALLOC
263+
# RUN: llc -mtriple=x86_64-pc-windows-msvc -o - %t/mov_after_dealloc.mir \
264+
# RUN: -run-pass=x86-wineh-unwindv2 -x86-wineh-unwindv2-force-mode=1 | \
265+
# RUN: FileCheck %s --check-prefix=BESTEFFORT
266+
# MOV-AFTER-DEALLOC: LLVM ERROR: Windows x64 Unwind v2 is required, but LLVM has generated incompatible code in function 'mov_after_dealloc':
267+
# MOV-AFTER-DEALLOC-SAME: Cannot set the frame back after the stack allocation has been deallocated
268+
269+
--- |
270+
define dso_local void @mov_after_dealloc() local_unnamed_addr {
271+
entry:
272+
ret void
273+
}
274+
!llvm.module.flags = !{!0}
275+
!0 = !{i32 1, !"winx64-eh-unwindv2", i32 2}
276+
...
277+
---
278+
name: mov_after_dealloc
279+
body: |
280+
bb.0.entry:
281+
$rbp = MOV64rr $rsp
282+
frame-setup SEH_SetFrame 52, 0
283+
$rsp = frame-setup SUB64ri32 $rsp, 40, implicit-def dead $eflags
284+
frame-setup SEH_StackAlloc 40
285+
frame-setup SEH_EndPrologue
286+
SEH_BeginEpilogue
287+
$rsp = frame-destroy ADD64ri32 $rsp, 40, implicit-def dead $eflags
288+
$rsp = MOV64rr $rbp
289+
SEH_EndEpilogue
290+
RET64
291+
...
292+
164293
;--- too_many_pops.mir
165294
# RUN: not --crash llc -mtriple=x86_64-pc-windows-msvc -o - %t/too_many_pops.mir \
166295
# RUN: -run-pass=x86-wineh-unwindv2 2>&1 | FileCheck %s \

llvm/test/CodeGen/X86/win64-eh-unwindv2.ll

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,9 +171,44 @@ define dso_local void @large_aligned_alloc() align 16 {
171171
; CHECK-NEXT: retq
172172
; CHECK-NEXT: .seh_endproc
173173

174+
define dso_local void @set_frame_only() local_unnamed_addr {
175+
tail call i64 @llvm.x86.flags.read.u64()
176+
ret void
177+
}
178+
179+
; CHECK-LABEL: set_frame_only:
180+
; CHECK: .seh_unwindversion 2
181+
; CHECK: .seh_pushreg %rbp
182+
; CHECK: .seh_setframe %rbp, 0
183+
; CHECK: .seh_endprologue
184+
; CHECK-NOT: .seh_endproc
185+
; CHECK: .seh_startepilogue
186+
; CHECK-NEXT: .seh_unwindv2start
187+
; CHECK-NEXT: popq %rbp
188+
; CHECK-NEXT: .seh_endepilogue
189+
; CHECK-NEXT: retq
190+
; CHECK-NEXT: .seh_endproc
191+
192+
attributes #1 = { noreturn }
193+
define dso_local void @no_return_func() local_unnamed_addr #1 {
194+
entry:
195+
call void @d()
196+
unreachable
197+
}
198+
; CHECK-LABEL: no_return_func:
199+
; CHECK-NOT: .seh_unwindversion 2
200+
; CHECK: .seh_stackalloc
201+
; CHECK-NEXT: .seh_endprologue
202+
; CHECK-NOT: .seh_startepilogue
203+
; CHECK-NOT: .seh_unwindv2start
204+
; CHECK: int3
205+
; CHECK-NEXT: .seh_endproc
206+
207+
declare i64 @llvm.x86.flags.read.u64()
174208
declare void @a() local_unnamed_addr
175209
declare i32 @b() local_unnamed_addr
176210
declare i32 @c(i32) local_unnamed_addr
211+
declare void @d() local_unnamed_addr #1
177212

178213
!llvm.module.flags = !{!0}
179-
!0 = !{i32 1, !"winx64-eh-unwindv2", i32 1}
214+
!0 = !{i32 1, !"winx64-eh-unwindv2", i32 2}

0 commit comments

Comments
 (0)