Skip to content

Commit 9a38192

Browse files
committed
[win][x64] Unwind v2: treat SetFrame differently from StackAlloc
* SetFrame does not imply that there was a stack allocation. * `mov` is used in the epilog to set the frame back, not to deallocate. It is also optional. * If the frame is set back in the epilog, then it must be done before deallocation and register popping.
1 parent d69ccde commit 9a38192

File tree

4 files changed

+201
-16
lines changed

4 files changed

+201
-16
lines changed

llvm/lib/MC/MCWin64EH.cpp

Lines changed: 11 additions & 6 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+
StringRef FunctionName;
2526
const MCSymbol *FunctionEnd;
2627
const MCSymbol *UnwindV2Start;
2728
const MCSymbol *EpilogEnd;
@@ -30,17 +31,19 @@ class MCUnwindV2EpilogTargetExpr final : public MCTargetExpr {
3031

3132
MCUnwindV2EpilogTargetExpr(const WinEH::FrameInfo &FrameInfo,
3233
const WinEH::FrameInfo::Epilog &Epilog,
33-
uint8_t EpilogSize_)
34-
: FunctionEnd(FrameInfo.FuncletOrFuncEnd),
34+
uint8_t EpilogSize_, StringRef FunctionName_)
35+
: FunctionName(FunctionName_), FunctionEnd(FrameInfo.FuncletOrFuncEnd),
3536
UnwindV2Start(Epilog.UnwindV2Start), EpilogEnd(Epilog.End),
3637
EpilogSize(EpilogSize_), Loc(Epilog.Loc) {}
3738

3839
public:
3940
static MCUnwindV2EpilogTargetExpr *
4041
create(const WinEH::FrameInfo &FrameInfo,
4142
const WinEH::FrameInfo::Epilog &Epilog, uint8_t EpilogSize_,
43+
StringRef FunctionName_,
4244
MCContext &Ctx) {
43-
return new (Ctx) MCUnwindV2EpilogTargetExpr(FrameInfo, Epilog, EpilogSize_);
45+
return new (Ctx) MCUnwindV2EpilogTargetExpr(FrameInfo, Epilog, EpilogSize_,
46+
FunctionName_);
4447
}
4548

4649
void printImpl(raw_ostream &OS, const MCAsmInfo *MAI) const override {
@@ -337,7 +340,7 @@ static void EmitUnwindInfo(MCStreamer &streamer, WinEH::FrameInfo *info) {
337340
// between the start of the epilog and the end of the function until
338341
// layout has been completed.
339342
auto *MCE = MCUnwindV2EpilogTargetExpr::create(*info, Epilog.second,
340-
EpilogSize, context);
343+
EpilogSize, info->Function->getName(), context);
341344
OS->addFixup(MCE, FK_Data_2);
342345
OS->appendContents(2, 0);
343346
}
@@ -383,14 +386,16 @@ bool MCUnwindV2EpilogTargetExpr::evaluateAsRelocatableImpl(
383386
auto Offset = GetOptionalAbsDifference(*Asm, FunctionEnd, UnwindV2Start);
384387
if (!Offset) {
385388
Asm->getContext().reportError(
386-
Loc, "Failed to evaluate epilog offset for Unwind v2");
389+
Loc, "Failed to evaluate epilog offset for Unwind v2 in " + FunctionName);
387390
return false;
388391
}
389392
assert(*Offset > 0);
390393
constexpr uint16_t MaxEpilogOffset = 0x0fff;
391394
if (*Offset > MaxEpilogOffset) {
395+
dbgs() << "Unwindv2: " << UnwindV2Start->getSection().getName();
396+
dbgs() << "FunctionEnd: " << FunctionEnd->getSection().getName();
392397
Asm->getContext().reportError(Loc,
393-
"Epilog offset is too large for Unwind v2");
398+
"Epilog offset is too large (0x" + Twine::utohexstr(*Offset) + ") for Unwind v2 in " + FunctionName);
394399
return false;
395400
}
396401

llvm/lib/Target/X86/X86WinEHUnwindV2.cpp

Lines changed: 40 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -90,10 +90,15 @@ DebugLoc findDebugLoc(const MachineBasicBlock &MBB) {
9090
}
9191

9292
bool X86WinEHUnwindV2::runOnMachineFunction(MachineFunction &MF) {
93+
// noreturn functions don't have epilogs.
94+
Function &F = MF.getFunction();
95+
if (F.doesNotReturn())
96+
return false;
97+
9398
WinX64EHUnwindV2Mode Mode =
9499
ForceMode.getNumOccurrences()
95100
? static_cast<WinX64EHUnwindV2Mode>(ForceMode.getValue())
96-
: MF.getFunction().getParent()->getWinX64EHUnwindV2Mode();
101+
: F.getParent()->getWinX64EHUnwindV2Mode();
97102

98103
if (Mode == WinX64EHUnwindV2Mode::Disabled)
99104
return false;
@@ -105,6 +110,7 @@ bool X86WinEHUnwindV2::runOnMachineFunction(MachineFunction &MF) {
105110
// Prolog information.
106111
SmallVector<int64_t> PushedRegs;
107112
bool HasStackAlloc = false;
113+
bool HasSetFrame = false;
108114
unsigned ApproximatePrologCodeCount = 0;
109115

110116
// Requested changes.
@@ -130,15 +136,20 @@ bool X86WinEHUnwindV2::runOnMachineFunction(MachineFunction &MF) {
130136
break;
131137

132138
case X86::SEH_StackAlloc:
133-
case X86::SEH_SetFrame:
134139
if (State != FunctionState::InProlog)
135-
llvm_unreachable("SEH_StackAlloc or SEH_SetFrame outside of prolog");
140+
llvm_unreachable("SEH_StackAlloc outside of prolog");
136141
// Assume a large alloc...
137-
ApproximatePrologCodeCount +=
138-
(MI.getOpcode() == X86::SEH_StackAlloc) ? 3 : 1;
142+
ApproximatePrologCodeCount += 3;
139143
HasStackAlloc = true;
140144
break;
141145

146+
case X86::SEH_SetFrame:
147+
if (State != FunctionState::InProlog)
148+
llvm_unreachable("SEH_SetFrame outside of prolog");
149+
ApproximatePrologCodeCount++;
150+
HasSetFrame = true;
151+
break;
152+
142153
case X86::SEH_SaveReg:
143154
case X86::SEH_SaveXMM:
144155
if (State != FunctionState::InProlog)
@@ -190,8 +201,30 @@ bool X86WinEHUnwindV2::runOnMachineFunction(MachineFunction &MF) {
190201
State = FunctionState::FinishedEpilog;
191202
break;
192203

193-
case X86::LEA64r:
194204
case X86::MOV64rr:
205+
if (State == FunctionState::InEpilog) {
206+
// If the prolog contains a stack allocation, then the first
207+
// instruction in the epilog must be to adjust the stack pointer.
208+
if (!HasSetFrame)
209+
return rejectCurrentFunctionInternalError(
210+
MF, Mode,
211+
"The epilog is setting frame back, but prolog did not set it");
212+
if (PoppedRegCount > 0)
213+
return rejectCurrentFunctionInternalError(
214+
MF, Mode,
215+
"The epilog is setting the frame back after popping "
216+
"registers");
217+
if (HasStackDealloc)
218+
return rejectCurrentFunctionInternalError(
219+
MF, Mode,
220+
"Cannot set the frame back after the stack "
221+
"allocation has been deallocated");
222+
} else if (State == FunctionState::FinishedEpilog)
223+
return rejectCurrentFunctionInternalError(
224+
MF, Mode, "Unexpected mov instruction after the epilog");
225+
break;
226+
227+
case X86::LEA64r:
195228
case X86::ADD64ri32:
196229
if (State == FunctionState::InEpilog) {
197230
// If the prolog contains a stack allocation, then the first
@@ -211,8 +244,7 @@ bool X86WinEHUnwindV2::runOnMachineFunction(MachineFunction &MF) {
211244
HasStackDealloc = true;
212245
} else if (State == FunctionState::FinishedEpilog)
213246
return rejectCurrentFunctionInternalError(
214-
MF, Mode,
215-
"Unexpected lea, mov or add instruction after the epilog");
247+
MF, Mode, "Unexpected lea or add instruction after the epilog");
216248
break;
217249

218250
case X86::POP64r:

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: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,9 +171,28 @@ 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+
declare i64 @llvm.x86.flags.read.u64()
174193
declare void @a() local_unnamed_addr
175194
declare i32 @b() local_unnamed_addr
176195
declare i32 @c(i32) local_unnamed_addr
177196

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

0 commit comments

Comments
 (0)