Skip to content

Commit

Permalink
Fix failure to invoke exception handler on Win64
Browse files Browse the repository at this point in the history
When the last instruction prior to a function epilogue is a call, we
need to emit a nop so that the return address is not in the epilogue IP
range.  This is consistent with MSVC's behavior, and may be a workaround
for a bug in the Win64 unwinder.

Differential Revision: http://reviews.llvm.org/D4751

Patch by Vadim Chugunov!

llvm-svn: 214775
  • Loading branch information
rnk committed Aug 4, 2014
1 parent 7c4851e commit e704010
Show file tree
Hide file tree
Showing 4 changed files with 113 additions and 0 deletions.
17 changes: 17 additions & 0 deletions llvm/lib/Target/X86/X86FrameLowering.cpp
Expand Up @@ -849,6 +849,11 @@ void X86FrameLowering::emitEpilogue(MachineFunction &MF,
unsigned FramePtr = RegInfo->getFrameRegister(MF);
unsigned StackPtr = RegInfo->getStackRegister();

bool IsWinEH =
MF.getTarget().getMCAsmInfo()->getExceptionHandlingType() ==
ExceptionHandling::WinEH;
bool NeedsWinEH = IsWinEH && MF.getFunction()->needsUnwindTableEntry();

switch (RetOpcode) {
default:
llvm_unreachable("Can only insert epilog into returning blocks");
Expand Down Expand Up @@ -933,17 +938,29 @@ void X86FrameLowering::emitEpilogue(MachineFunction &MF,
unsigned Opc = getLEArOpcode(IsLP64);
addRegOffset(BuildMI(MBB, MBBI, DL, TII.get(Opc), StackPtr),
FramePtr, false, -CSSize);
--MBBI;
} else {
unsigned Opc = (Is64Bit ? X86::MOV64rr : X86::MOV32rr);
BuildMI(MBB, MBBI, DL, TII.get(Opc), StackPtr)
.addReg(FramePtr);
--MBBI;
}
} else if (NumBytes) {
// Adjust stack pointer back: ESP += numbytes.
emitSPUpdate(MBB, MBBI, StackPtr, NumBytes, Is64Bit, IsLP64, UseLEA,
TII, *RegInfo);
--MBBI;
}

// Windows unwinder will not invoke function's exception handler if IP is
// either in prologue or in epilogue. This behavior causes a problem when a
// call immediately precedes an epilogue, because the return address points
// into the epilogue. To cope with that, we insert an epilogue marker here,
// then replace it with a 'nop' if it ends up immediately after a CALL in the
// final emitted code.
if (NeedsWinEH)
BuildMI(MBB, MBBI, DL, TII.get(X86::SEH_Epilogue));

// We're returning from function via eh_return.
if (RetOpcode == X86::EH_RETURN || RetOpcode == X86::EH_RETURN64) {
MBBI = MBB.getLastNonDebugInstr();
Expand Down
2 changes: 2 additions & 0 deletions llvm/lib/Target/X86/X86InstrCompiler.td
Expand Up @@ -214,6 +214,8 @@ let isPseudo = 1 in {
"#SEH_PushFrame $mode", []>;
def SEH_EndPrologue : I<0, Pseudo, (outs), (ins),
"#SEH_EndPrologue", []>;
def SEH_Epilogue : I<0, Pseudo, (outs), (ins),
"#SEH_Epilogue", []>;
}

//===----------------------------------------------------------------------===//
Expand Down
29 changes: 29 additions & 0 deletions llvm/lib/Target/X86/X86MCInstLower.cpp
Expand Up @@ -826,6 +826,20 @@ void X86AsmPrinter::LowerPATCHPOINT(const MachineInstr &MI) {
getSubtargetInfo());
}

// Returns instruction preceding MBBI in MachineFunction.
// If MBBI is the first instruction of the first basic block, returns null.
static MachineBasicBlock::const_iterator
PrevCrossBBInst(MachineBasicBlock::const_iterator MBBI) {
const MachineBasicBlock *MBB = MBBI->getParent();
while (MBBI == MBB->begin()) {
if (MBB == MBB->getParent()->begin())
return nullptr;
MBB = MBB->getPrevNode();
MBBI = MBB->end();
}
return --MBBI;
}

void X86AsmPrinter::EmitInstruction(const MachineInstr *MI) {
X86MCInstLower MCInstLowering(*MF, *this);
const X86RegisterInfo *RI =
Expand Down Expand Up @@ -967,6 +981,21 @@ void X86AsmPrinter::EmitInstruction(const MachineInstr *MI) {
OutStreamer.EmitWinCFIEndProlog();
return;

case X86::SEH_Epilogue: {
MachineBasicBlock::const_iterator MBBI(MI);
// Check if preceded by a call and emit nop if so.
for (MBBI = PrevCrossBBInst(MBBI); MBBI; MBBI = PrevCrossBBInst(MBBI)) {
// Conservatively assume that pseudo instructions don't emit code and keep
// looking for a call. We may emit an unnecessary nop in some cases.
if (!MBBI->isPseudo()) {
if (MBBI->isCall())
EmitAndCountInstruction(MCInstBuilder(X86::NOOP));
break;
}
}
return;
}

case X86::PSHUFBrm:
case X86::VPSHUFBrm:
// Lower PSHUFB normally but add a comment if we can find a constant
Expand Down
65 changes: 65 additions & 0 deletions llvm/test/CodeGen/X86/win64_call_epi.ll
@@ -0,0 +1,65 @@
; RUN: llc < %s -mtriple=x86_64-pc-mingw32 | FileCheck %s -check-prefix=WIN64

declare void @bar()
declare void @baz()
declare i32 @personality(...)

; Check for 'nop' between the last call and the epilogue.
define void @foo1() {

invoke void @bar()
to label %normal
unwind label %catch

normal:
ret void

catch:
%1 = landingpad { i8*, i32 } personality i32 (...)* @personality cleanup
resume { i8*, i32 } %1
}
; WIN64-LABEL: foo1:
; WIN64: .seh_proc foo1
; WIN64: callq bar
; WIN64: nop
; WIN64: addq ${{[0-9]+}}, %rsp
; WIN64: retq
; Check for 'ud2' after noreturn call
; WIN64: callq _Unwind_Resume
; WIN64-NEXT: ud2
; WIN64: .seh_endproc


; Check it still works when blocks are reordered.
@something = global i32 0
define void @foo2(i1 zeroext %cond ) {
br i1 %cond, label %a, label %b, !prof !0
a:
call void @bar()
br label %done
b:
call void @baz()
store i32 0, i32* @something
br label %done
done:
ret void
}
!0 = metadata !{metadata !"branch_weights", i32 100, i32 0}
; WIN64-LABEL: foo2:
; WIN64: callq bar
; WIN64: nop
; WIN64: addq ${{[0-9]+}}, %rsp
; WIN64: retq


; Check nop is not emitted when call is not adjacent to epilogue.
define i32 @foo3() {
call void @bar()
ret i32 0
}
; WIN64-LABEL: foo3:
; WIN64: callq bar
; WIN64: xorl
; WIN64-NOT: nop
; WIN64: addq ${{[0-9]+}}, %rsp
; WIN64: retq

0 comments on commit e704010

Please sign in to comment.