Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 15 additions & 9 deletions llvm/lib/MC/MCWin64EH.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ class MCSection;

/// MCExpr that represents the epilog unwind code in an unwind table.
class MCUnwindV2EpilogTargetExpr final : public MCTargetExpr {
const MCSymbol *Function;
const MCSymbol *FunctionEnd;
const MCSymbol *UnwindV2Start;
const MCSymbol *EpilogEnd;
Expand All @@ -31,7 +32,7 @@ class MCUnwindV2EpilogTargetExpr final : public MCTargetExpr {
MCUnwindV2EpilogTargetExpr(const WinEH::FrameInfo &FrameInfo,
const WinEH::FrameInfo::Epilog &Epilog,
uint8_t EpilogSize_)
: FunctionEnd(FrameInfo.FuncletOrFuncEnd),
: Function(FrameInfo.Function), FunctionEnd(FrameInfo.FuncletOrFuncEnd),
UnwindV2Start(Epilog.UnwindV2Start), EpilogEnd(Epilog.End),
EpilogSize(EpilogSize_), Loc(Epilog.Loc) {}

Expand Down Expand Up @@ -253,13 +254,15 @@ static void EmitUnwindInfo(MCStreamer &streamer, WinEH::FrameInfo *info) {
OS->getAssembler(), LastEpilog.End, LastEpilog.UnwindV2Start);
if (!MaybeSize) {
context.reportError(LastEpilog.Loc,
"Failed to evaluate epilog size for Unwind v2");
"Failed to evaluate epilog size for Unwind v2 in " +
info->Function->getName());
return;
}
assert(*MaybeSize >= 0);
if (*MaybeSize >= (int64_t)UINT8_MAX) {
context.reportError(LastEpilog.Loc,
"Epilog size is too large for Unwind v2");
"Epilog size is too large for Unwind v2 in " +
info->Function->getName());
return;
}
EpilogSize = *MaybeSize + 1;
Expand All @@ -282,7 +285,8 @@ static void EmitUnwindInfo(MCStreamer &streamer, WinEH::FrameInfo *info) {
// Too many epilogs to handle.
if ((size_t)numCodes + numEpilogCodes > UINT8_MAX) {
context.reportError(info->FunctionLoc,
"Too many unwind codes with Unwind v2 enabled");
"Too many unwind codes with Unwind v2 enabled in " +
info->Function->getName());
return;
}

Expand Down Expand Up @@ -383,23 +387,25 @@ bool MCUnwindV2EpilogTargetExpr::evaluateAsRelocatableImpl(
auto Offset = GetOptionalAbsDifference(*Asm, FunctionEnd, UnwindV2Start);
if (!Offset) {
Asm->getContext().reportError(
Loc, "Failed to evaluate epilog offset for Unwind v2");
Loc, "Failed to evaluate epilog offset for Unwind v2 in " +
Function->getName());
return false;
}
assert(*Offset > 0);
constexpr uint16_t MaxEpilogOffset = 0x0fff;
if (*Offset > MaxEpilogOffset) {
Asm->getContext().reportError(Loc,
"Epilog offset is too large for Unwind v2");
Asm->getContext().reportError(
Loc,
"Epilog offset is too large for Unwind v2 in " + Function->getName());
return false;
}

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

Expand Down
46 changes: 35 additions & 11 deletions llvm/lib/Target/X86/X86WinEHUnwindV2.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ bool X86WinEHUnwindV2::runOnMachineFunction(MachineFunction &MF) {
// Prolog information.
SmallVector<int64_t> PushedRegs;
bool HasStackAlloc = false;
bool HasSetFrame = false;
unsigned ApproximatePrologCodeCount = 0;

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

case X86::SEH_StackAlloc:
case X86::SEH_SetFrame:
if (State != FunctionState::InProlog)
llvm_unreachable("SEH_StackAlloc or SEH_SetFrame outside of prolog");
llvm_unreachable("SEH_StackAlloc outside of prolog");
// Assume a large alloc...
ApproximatePrologCodeCount +=
(MI.getOpcode() == X86::SEH_StackAlloc) ? 3 : 1;
ApproximatePrologCodeCount += 3;
HasStackAlloc = true;
break;

case X86::SEH_SetFrame:
if (State != FunctionState::InProlog)
llvm_unreachable("SEH_SetFrame outside of prolog");
ApproximatePrologCodeCount++;
HasSetFrame = true;
break;

case X86::SEH_SaveReg:
case X86::SEH_SaveXMM:
if (State != FunctionState::InProlog)
Expand Down Expand Up @@ -190,8 +196,30 @@ bool X86WinEHUnwindV2::runOnMachineFunction(MachineFunction &MF) {
State = FunctionState::FinishedEpilog;
break;

case X86::LEA64r:
case X86::MOV64rr:
if (State == FunctionState::InEpilog) {
// If the prolog contains a stack allocation, then the first
// instruction in the epilog must be to adjust the stack pointer.
if (!HasSetFrame)
return rejectCurrentFunctionInternalError(
MF, Mode,
"The epilog is setting frame back, but prolog did not set it");
if (PoppedRegCount > 0)
return rejectCurrentFunctionInternalError(
MF, Mode,
"The epilog is setting the frame back after popping "
"registers");
if (HasStackDealloc)
return rejectCurrentFunctionInternalError(
MF, Mode,
"Cannot set the frame back after the stack "
"allocation has been deallocated");
} else if (State == FunctionState::FinishedEpilog)
return rejectCurrentFunctionInternalError(
MF, Mode, "Unexpected mov instruction after the epilog");
break;

case X86::LEA64r:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can an LEA64r correspond to a SetFrame? (I think we might generate lea 16(%rbp), %rsp or something like that.) Not sure how much it matters.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only if there was a SetFrame AND a stack alloc, otherwise MOV is used:

unsigned SEHFrameOffset = calculateSetFPREG(SEHStackAllocAmt);
uint64_t LEAAmount =
IsWin64Prologue ? SEHStackAllocAmt - SEHFrameOffset : -CSSize;
if (X86FI->hasSwiftAsyncContext())
LEAAmount -= 16;
// There are only two legal forms of epilogue:
// - add SEHAllocationSize, %rsp
// - lea SEHAllocationSize(%FramePtr), %rsp
//
// 'mov %FramePtr, %rsp' will not be recognized as an epilogue sequence.
// However, we may use this sequence if we have a frame pointer because the
// effects of the prologue can safely be undone.
if (LEAAmount != 0) {
unsigned Opc = getLEArOpcode(Uses64BitFramePtr);
addRegOffset(BuildMI(MBB, MBBI, DL, TII.get(Opc), StackPtr), FramePtr,
false, LEAAmount);
--MBBI;
} else {
unsigned Opc = (Uses64BitFramePtr ? X86::MOV64rr : X86::MOV32rr);
BuildMI(MBB, MBBI, DL, TII.get(Opc), StackPtr).addReg(FramePtr);
--MBBI;
}

case X86::ADD64ri32:
if (State == FunctionState::InEpilog) {
// If the prolog contains a stack allocation, then the first
Expand All @@ -211,8 +239,7 @@ bool X86WinEHUnwindV2::runOnMachineFunction(MachineFunction &MF) {
HasStackDealloc = true;
} else if (State == FunctionState::FinishedEpilog)
return rejectCurrentFunctionInternalError(
MF, Mode,
"Unexpected lea, mov or add instruction after the epilog");
MF, Mode, "Unexpected lea or add instruction after the epilog");
break;

case X86::POP64r:
Expand Down Expand Up @@ -278,11 +305,8 @@ bool X86WinEHUnwindV2::runOnMachineFunction(MachineFunction &MF) {
}
}

if (UnwindV2StartLocations.empty()) {
assert(State == FunctionState::InProlog &&
"If there are no epilogs, then there should be no prolog");
if (UnwindV2StartLocations.empty())
return false;
}

MachineBasicBlock &FirstMBB = MF.front();
// Assume +1 for the "header" UOP_Epilog that contains the epilog size, and
Expand Down
131 changes: 130 additions & 1 deletion llvm/test/CodeGen/X86/win64-eh-unwindv2-errors.mir
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ body: |
# RUN: -x86-wineh-unwindv2-force-mode=1 | FileCheck %s \
# RUN: --check-prefix=BESTEFFORT
# DEALLOC-AFTER-EPILOG: LLVM ERROR: Windows x64 Unwind v2 is required, but LLVM has generated incompatible code in function 'dealloc_after_epilog':
# DEALLOC-AFTER-EPILOG-SAME: Unexpected lea, mov or add instruction after the epilog
# DEALLOC-AFTER-EPILOG-SAME: Unexpected lea or add instruction after the epilog

--- |
define dso_local void @dealloc_after_epilog() local_unnamed_addr {
Expand Down Expand Up @@ -161,6 +161,135 @@ body: |
RET64
...

;--- mov_no_setframe.mir
# RUN: not --crash llc -mtriple=x86_64-pc-windows-msvc -o - \
# RUN: %t/mov_no_setframe.mir -run-pass=x86-wineh-unwindv2 2>&1 | \
# RUN: FileCheck %s --check-prefix=MOV-NO-SETFRAME
# RUN: llc -mtriple=x86_64-pc-windows-msvc -o - %t/mov_no_setframe.mir \
# RUN: -run-pass=x86-wineh-unwindv2 -x86-wineh-unwindv2-force-mode=1 | \
# RUN: FileCheck %s --check-prefix=BESTEFFORT
# MOV-NO-SETFRAME: LLVM ERROR: Windows x64 Unwind v2 is required, but LLVM has generated incompatible code in function 'mov_no_setframe':
# MOV-NO-SETFRAME-SAME: The epilog is setting frame back, but prolog did not set it

--- |
define dso_local void @mov_no_setframe() local_unnamed_addr {
entry:
ret void
}
!llvm.module.flags = !{!0}
!0 = !{i32 1, !"winx64-eh-unwindv2", i32 2}
...
---
name: mov_no_setframe
body: |
bb.0.entry:
frame-setup SEH_EndPrologue
SEH_BeginEpilogue
$rsp = MOV64rr $rbp
SEH_EndEpilogue
RET64
...

;--- mov_after_epilog.mir
# RUN: not --crash llc -mtriple=x86_64-pc-windows-msvc -o - \
# RUN: %t/mov_after_epilog.mir -run-pass=x86-wineh-unwindv2 2>&1 | \
# RUN: FileCheck %s --check-prefix=MOV-AFTER-EPILOG
# RUN: llc -mtriple=x86_64-pc-windows-msvc -o - \
# RUN: %t/mov_after_epilog.mir -run-pass=x86-wineh-unwindv2 \
# RUN: -x86-wineh-unwindv2-force-mode=1 | FileCheck %s \
# RUN: --check-prefix=BESTEFFORT
# MOV-AFTER-EPILOG: LLVM ERROR: Windows x64 Unwind v2 is required, but LLVM has generated incompatible code in function 'mov_after_epilog':
# MOV-AFTER-EPILOG-SAME: Unexpected mov instruction after the epilog

--- |
define dso_local void @mov_after_epilog() local_unnamed_addr {
entry:
ret void
}
!llvm.module.flags = !{!0}
!0 = !{i32 1, !"winx64-eh-unwindv2", i32 2}
...
---
name: mov_after_epilog
body: |
bb.0.entry:
$rbp = MOV64rr $rsp
frame-setup SEH_SetFrame 52, 0
frame-setup SEH_EndPrologue
SEH_BeginEpilogue
SEH_EndEpilogue
$rsp = MOV64rr $rbp
RET64
...

;--- pop_before_mov.mir
# RUN: not --crash llc -mtriple=x86_64-pc-windows-msvc -o - \
# RUN: %t/pop_before_mov.mir -run-pass=x86-wineh-unwindv2 2>&1 | \
# RUN: FileCheck %s --check-prefix=POP-BEFORE-MOV
# RUN: llc -mtriple=x86_64-pc-windows-msvc -o - %t/pop_before_mov.mir \
# RUN: -run-pass=x86-wineh-unwindv2 -x86-wineh-unwindv2-force-mode=1 | \
# RUN: FileCheck %s --check-prefix=BESTEFFORT
# POP-BEFORE-MOV: LLVM ERROR: Windows x64 Unwind v2 is required, but LLVM has generated incompatible code in function 'pop_before_mov':
# POP-BEFORE-MOV-SAME: The epilog is setting the frame back after popping registers

--- |
define dso_local void @pop_before_mov() local_unnamed_addr {
entry:
ret void
}
!llvm.module.flags = !{!0}
!0 = !{i32 1, !"winx64-eh-unwindv2", i32 2}
...
---
name: pop_before_mov
body: |
bb.0.entry:
frame-setup PUSH64r killed $rdi, implicit-def $rsp, implicit $rsp
frame-setup SEH_PushReg 55
$rbp = MOV64rr $rsp
frame-setup SEH_SetFrame 52, 0
frame-setup SEH_EndPrologue
SEH_BeginEpilogue
$rdi = frame-destroy POP64r implicit-def $rsp, implicit $rsp
$rsp = MOV64rr $rbp
SEH_EndEpilogue
RET64
...

;--- mov_after_dealloc.mir
# RUN: not --crash llc -mtriple=x86_64-pc-windows-msvc -o - \
# RUN: %t/mov_after_dealloc.mir -run-pass=x86-wineh-unwindv2 2>&1 | \
# RUN: FileCheck %s --check-prefix=MOV-AFTER-DEALLOC
# RUN: llc -mtriple=x86_64-pc-windows-msvc -o - %t/mov_after_dealloc.mir \
# RUN: -run-pass=x86-wineh-unwindv2 -x86-wineh-unwindv2-force-mode=1 | \
# RUN: FileCheck %s --check-prefix=BESTEFFORT
# MOV-AFTER-DEALLOC: LLVM ERROR: Windows x64 Unwind v2 is required, but LLVM has generated incompatible code in function 'mov_after_dealloc':
# MOV-AFTER-DEALLOC-SAME: Cannot set the frame back after the stack allocation has been deallocated

--- |
define dso_local void @mov_after_dealloc() local_unnamed_addr {
entry:
ret void
}
!llvm.module.flags = !{!0}
!0 = !{i32 1, !"winx64-eh-unwindv2", i32 2}
...
---
name: mov_after_dealloc
body: |
bb.0.entry:
$rbp = MOV64rr $rsp
frame-setup SEH_SetFrame 52, 0
$rsp = frame-setup SUB64ri32 $rsp, 40, implicit-def dead $eflags
frame-setup SEH_StackAlloc 40
frame-setup SEH_EndPrologue
SEH_BeginEpilogue
$rsp = frame-destroy ADD64ri32 $rsp, 40, implicit-def dead $eflags
$rsp = MOV64rr $rbp
SEH_EndEpilogue
RET64
...

;--- too_many_pops.mir
# RUN: not --crash llc -mtriple=x86_64-pc-windows-msvc -o - %t/too_many_pops.mir \
# RUN: -run-pass=x86-wineh-unwindv2 2>&1 | FileCheck %s \
Expand Down
37 changes: 36 additions & 1 deletion llvm/test/CodeGen/X86/win64-eh-unwindv2.ll
Original file line number Diff line number Diff line change
Expand Up @@ -171,9 +171,44 @@ define dso_local void @large_aligned_alloc() align 16 {
; CHECK-NEXT: retq
; CHECK-NEXT: .seh_endproc

define dso_local void @set_frame_only() local_unnamed_addr {
tail call i64 @llvm.x86.flags.read.u64()
ret void
}

; CHECK-LABEL: set_frame_only:
; CHECK: .seh_unwindversion 2
; CHECK: .seh_pushreg %rbp
; CHECK: .seh_setframe %rbp, 0
; CHECK: .seh_endprologue
; CHECK-NOT: .seh_endproc
; CHECK: .seh_startepilogue
; CHECK-NEXT: .seh_unwindv2start
; CHECK-NEXT: popq %rbp
; CHECK-NEXT: .seh_endepilogue
; CHECK-NEXT: retq
; CHECK-NEXT: .seh_endproc

attributes #1 = { noreturn }
define dso_local void @no_return_func() local_unnamed_addr #1 {
entry:
call void @d()
unreachable
}
; CHECK-LABEL: no_return_func:
; CHECK-NOT: .seh_unwindversion 2
; CHECK: .seh_stackalloc
; CHECK-NEXT: .seh_endprologue
; CHECK-NOT: .seh_startepilogue
; CHECK-NOT: .seh_unwindv2start
; CHECK: int3
; CHECK-NEXT: .seh_endproc

declare i64 @llvm.x86.flags.read.u64()
declare void @a() local_unnamed_addr
declare i32 @b() local_unnamed_addr
declare i32 @c(i32) local_unnamed_addr
declare void @d() local_unnamed_addr #1

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