Skip to content
Closed
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
17 changes: 11 additions & 6 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 {
StringRef FunctionName;
const MCSymbol *FunctionEnd;
const MCSymbol *UnwindV2Start;
const MCSymbol *EpilogEnd;
Expand All @@ -30,17 +31,19 @@ class MCUnwindV2EpilogTargetExpr final : public MCTargetExpr {

MCUnwindV2EpilogTargetExpr(const WinEH::FrameInfo &FrameInfo,
const WinEH::FrameInfo::Epilog &Epilog,
uint8_t EpilogSize_)
: FunctionEnd(FrameInfo.FuncletOrFuncEnd),
uint8_t EpilogSize_, StringRef FunctionName_)
: FunctionName(FunctionName_), FunctionEnd(FrameInfo.FuncletOrFuncEnd),
UnwindV2Start(Epilog.UnwindV2Start), EpilogEnd(Epilog.End),
EpilogSize(EpilogSize_), Loc(Epilog.Loc) {}

public:
static MCUnwindV2EpilogTargetExpr *
create(const WinEH::FrameInfo &FrameInfo,
const WinEH::FrameInfo::Epilog &Epilog, uint8_t EpilogSize_,
StringRef FunctionName_,
MCContext &Ctx) {
return new (Ctx) MCUnwindV2EpilogTargetExpr(FrameInfo, Epilog, EpilogSize_);
return new (Ctx) MCUnwindV2EpilogTargetExpr(FrameInfo, Epilog, EpilogSize_,
FunctionName_);
}

void printImpl(raw_ostream &OS, const MCAsmInfo *MAI) const override {
Expand Down Expand Up @@ -337,7 +340,7 @@ static void EmitUnwindInfo(MCStreamer &streamer, WinEH::FrameInfo *info) {
// between the start of the epilog and the end of the function until
// layout has been completed.
auto *MCE = MCUnwindV2EpilogTargetExpr::create(*info, Epilog.second,
EpilogSize, context);
EpilogSize, info->Function->getName(), context);
OS->addFixup(MCE, FK_Data_2);
OS->appendContents(2, 0);
}
Expand Down Expand Up @@ -383,14 +386,16 @@ 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 " + FunctionName);
return false;
}
assert(*Offset > 0);
constexpr uint16_t MaxEpilogOffset = 0x0fff;
if (*Offset > MaxEpilogOffset) {
dbgs() << "Unwindv2: " << UnwindV2Start->getSection().getName();
dbgs() << "FunctionEnd: " << FunctionEnd->getSection().getName();
Asm->getContext().reportError(Loc,
"Epilog offset is too large for Unwind v2");
"Epilog offset is too large (0x" + Twine::utohexstr(*Offset) + ") for Unwind v2 in " + FunctionName);
return false;
}

Expand Down
48 changes: 40 additions & 8 deletions llvm/lib/Target/X86/X86WinEHUnwindV2.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -90,10 +90,15 @@ DebugLoc findDebugLoc(const MachineBasicBlock &MBB) {
}

bool X86WinEHUnwindV2::runOnMachineFunction(MachineFunction &MF) {
// noreturn functions don't have epilogs.
Function &F = MF.getFunction();
if (F.doesNotReturn())
return false;

WinX64EHUnwindV2Mode Mode =
ForceMode.getNumOccurrences()
? static_cast<WinX64EHUnwindV2Mode>(ForceMode.getValue())
: MF.getFunction().getParent()->getWinX64EHUnwindV2Mode();
: F.getParent()->getWinX64EHUnwindV2Mode();

if (Mode == WinX64EHUnwindV2Mode::Disabled)
return false;
Expand All @@ -105,6 +110,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 +136,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 +201,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:
case X86::ADD64ri32:
if (State == FunctionState::InEpilog) {
// If the prolog contains a stack allocation, then the first
Expand All @@ -211,8 +244,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
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
21 changes: 20 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,28 @@ 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

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

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