[PATCH][MC/X86_64] Implement Win64 exception handling

Kai Nacke kai.nacke at redstar.de
Sun Nov 17 07:46:05 PST 2013


Hi all,

this patch implements Dwarf exception handling for Windows 64 based on 
the structured exception handling facility provided by the OS. This is 
similar to the way exception handling is implemented in gcc 4.8 
(mingw64) on Windows 64 and is meant to be compatible to this.

The first patch implements the emission of unwind info. It also emits 
the personality function as SEH handler function and the Dwarf code as 
SEH handler data. (gcc 4.8 does the same.)

The second patch is only a proof of concept. It shows the basic changes 
to clang in order to use the EH implementation. (The change to the tool 
chain is missing.) The required implementation in compiler-rt is also 
missing (implementation of __gcc_personality_seh0 and 
__gxx_personality_seh0). Instead, the mingw64 libunwind library must be 
used.

A working personality function implemented in D can be found here:
https://github.com/ldc-developers/druntime/blob/ldc/src/ldc/eh2.d

A consequence of this patch is that Win64 ABI errors become more serious.
E.g. because of PR16779 I had to set the test avx-win64-args.ll to XFAIL.

Last but not least most of this patch is based on work by Charles Davis.

Please review.

Regards,
Kai
-------------- next part --------------
>From 0b983937013f8fd86bb2f6f1aca8ddd4a70ce578 Mon Sep 17 00:00:00 2001
From: kai <kai at redstar.de>
Date: Wed, 29 May 2013 07:10:59 +0200
Subject: [PATCH] Add Win64 exception handling.

This patch enables Dwarf exception handling on Windows 64 based on the
structured exception handling (SEH) mechanism built into the OS. This
is similar to the way exception handling is implemented in gcc 4.8
(mingw64) on Windows 64.

For each instruction in the frame setup range, the needed unwind
information is emitted. In order to match the gcc implementation, the
personality function is emitted as handler function and the Dwarf code
is emitted as handler data.

Most of this patch is based on work by Charles Davis.
---
 lib/CodeGen/AsmPrinter/Win64Exception.cpp    |  12 +--
 lib/MC/MCObjectFileInfo.cpp                  |  15 ++--
 lib/Target/X86/MCTargetDesc/X86MCAsmInfo.cpp |   7 +-
 lib/Target/X86/X86AsmPrinter.h               |   2 +
 lib/Target/X86/X86ISelLowering.cpp           |   3 +-
 lib/Target/X86/X86MCInstLower.cpp            | 100 +++++++++++++++++++++
 test/CodeGen/X86/avx-win64-args.ll           |   2 +
 test/CodeGen/X86/win64_eh.ll                 | 125 +++++++++++++++++++++++++++
 8 files changed, 250 insertions(+), 16 deletions(-)
 create mode 100644 test/CodeGen/X86/win64_eh.ll

diff --git a/lib/CodeGen/AsmPrinter/Win64Exception.cpp b/lib/CodeGen/AsmPrinter/Win64Exception.cpp
index 1561012..99d53b9 100644
--- a/lib/CodeGen/AsmPrinter/Win64Exception.cpp
+++ b/lib/CodeGen/AsmPrinter/Win64Exception.cpp
@@ -78,9 +78,9 @@ void Win64Exception::BeginFunction(const MachineFunction *MF) {
   if (!shouldEmitPersonality)
     return;
 
-  MCSymbol *GCCHandlerSym =
-    Asm->GetExternalSymbolSymbol("_GCC_specific_handler");
-  Asm->OutStreamer.EmitWin64EHHandler(GCCHandlerSym, true, true);
+  const MCSymbol *PersHandlerSym = TLOF.getCFIPersonalitySymbol(Per, Asm->Mang,
+                                                                MMI);
+  Asm->OutStreamer.EmitWin64EHHandler(PersHandlerSym, true, true);
 
   Asm->OutStreamer.EmitLabel(Asm->GetTempSymbol("eh_func_begin",
                                                 Asm->getFunctionNumber()));
@@ -99,14 +99,8 @@ void Win64Exception::EndFunction() {
   MMI->TidyLandingPads();
 
   if (shouldEmitPersonality) {
-    const TargetLoweringObjectFile &TLOF = Asm->getObjFileLowering();
-    const Function *Per = MMI->getPersonalities()[MMI->getPersonalityIndex()];
-    const MCSymbol *Sym = TLOF.getCFIPersonalitySymbol(Per, Asm->Mang, MMI);
-
     Asm->OutStreamer.PushSection();
     Asm->OutStreamer.EmitWin64EHHandlerData();
-    Asm->OutStreamer.EmitValue(MCSymbolRefExpr::Create(Sym, Asm->OutContext),
-                               4);
     EmitExceptionTable();
     Asm->OutStreamer.PopSection();
   }
diff --git a/lib/MC/MCObjectFileInfo.cpp b/lib/MC/MCObjectFileInfo.cpp
index 8ef4a0a..3de482f 100644
--- a/lib/MC/MCObjectFileInfo.cpp
+++ b/lib/MC/MCObjectFileInfo.cpp
@@ -574,11 +574,16 @@ void MCObjectFileInfo::InitCOFFMCObjectFileInfo(Triple T) {
   // though it contains relocatable pointers.  In PIC mode, this is probably a
   // big runtime hit for C++ apps.  Either the contents of the LSDA need to be
   // adjusted or this should be a data section.
-  LSDASection =
-    Ctx->getCOFFSection(".gcc_except_table",
-                        COFF::IMAGE_SCN_CNT_INITIALIZED_DATA |
-                        COFF::IMAGE_SCN_MEM_READ,
-                        SectionKind::getReadOnly());
+  if (T.getOS() == Triple::Win32) {
+    // On Windows with SEH, the LSDA is emitted into the .xdata section
+    LSDASection = 0;
+  } else {
+    LSDASection =
+      Ctx->getCOFFSection(".gcc_except_table",
+                          COFF::IMAGE_SCN_CNT_INITIALIZED_DATA |
+                          COFF::IMAGE_SCN_MEM_READ,
+                          SectionKind::getReadOnly());
+  }
 
   // Debug info.
   DwarfAbbrevSection =
diff --git a/lib/Target/X86/MCTargetDesc/X86MCAsmInfo.cpp b/lib/Target/X86/MCTargetDesc/X86MCAsmInfo.cpp
index 3861e1c..ca413f5 100644
--- a/lib/Target/X86/MCTargetDesc/X86MCAsmInfo.cpp
+++ b/lib/Target/X86/MCTargetDesc/X86MCAsmInfo.cpp
@@ -127,7 +127,8 @@ getNonexecutableStackSection(MCContext &Ctx) const {
 void X86MCAsmInfoMicrosoft::anchor() { }
 
 X86MCAsmInfoMicrosoft::X86MCAsmInfoMicrosoft(const Triple &Triple) {
-  if (Triple.getArch() == Triple::x86_64) {
+  bool is64Bit = Triple.getArch() == Triple::x86_64;
+  if (is64Bit) {
     GlobalPrefix = "";
     PrivateGlobalPrefix = ".L";
   }
@@ -137,6 +138,10 @@ X86MCAsmInfoMicrosoft::X86MCAsmInfoMicrosoft(const Triple &Triple) {
   TextAlignFillValue = 0x90;
 
   AllowAtInName = true;
+
+  // Exceptions handling
+  if (is64Bit)
+    ExceptionsType = ExceptionHandling::Win64;
 }
 
 void X86MCAsmInfoGNUCOFF::anchor() { }
diff --git a/lib/Target/X86/X86AsmPrinter.h b/lib/Target/X86/X86AsmPrinter.h
index 24a768b..78531a9 100644
--- a/lib/Target/X86/X86AsmPrinter.h
+++ b/lib/Target/X86/X86AsmPrinter.h
@@ -37,6 +37,8 @@ class LLVM_LIBRARY_VISIBILITY X86AsmPrinter : public AsmPrinter {
                           MachineInstr::const_mop_iterator MOE,
                           const TargetMachine &TM);
 
+  void EmitUnwindingInstruction(const MachineInstr *MI);
+
  public:
   explicit X86AsmPrinter(TargetMachine &TM, MCStreamer &Streamer)
     : AsmPrinter(TM, Streamer), SM(*this, stackmapOperandParser) {
diff --git a/lib/Target/X86/X86ISelLowering.cpp b/lib/Target/X86/X86ISelLowering.cpp
index 9df0232..3e59825 100644
--- a/lib/Target/X86/X86ISelLowering.cpp
+++ b/lib/Target/X86/X86ISelLowering.cpp
@@ -596,7 +596,8 @@ void X86TargetLowering::resetOperationActions() {
   // FIXME - use subtarget debug flags
   if (!Subtarget->isTargetDarwin() &&
       !Subtarget->isTargetELF() &&
-      !Subtarget->isTargetCygMing()) {
+      !Subtarget->isTargetCygMing() &&
+      !Subtarget->isTargetWin64()) {
     setOperationAction(ISD::EH_LABEL, MVT::Other, Expand);
   }
 
diff --git a/lib/Target/X86/X86MCInstLower.cpp b/lib/Target/X86/X86MCInstLower.cpp
index 4e27ef0..6904aee 100644
--- a/lib/Target/X86/X86MCInstLower.cpp
+++ b/lib/Target/X86/X86MCInstLower.cpp
@@ -853,7 +853,100 @@ static void LowerPATCHPOINT(MCStreamer &OutStreamer,
     OutStreamer.EmitInstruction(MCInstBuilder(X86::NOOP));
 }
 
+static bool isCalleeSavedReg(const MachineFunction *MF,
+                             const TargetRegisterInfo *RI, unsigned Reg) {
+  const uint16_t *NVRegs = RI->getCalleeSavedRegs(MF);
+  while (*NVRegs) {
+    if (*NVRegs == Reg)
+      return true;
+    NVRegs++;
+  }
+  return false;
+}
+
+void X86AsmPrinter::EmitUnwindingInstruction(const MachineInstr *MI) {
+  assert(MI->getFlag(MachineInstr::FrameSetup) &&
+         "Only call frame setup instructions allowed here!");
+  unsigned SReg, DReg;
+  unsigned Offset;
+  const X86RegisterInfo *RI =
+    static_cast<const X86RegisterInfo *>(TM.getRegisterInfo());
+  switch (MI->getOpcode()) {
+  default: llvm_unreachable("Unknown frame setup opcode!");
+  case X86::PUSH64r:
+    SReg = MI->getOperand(0).getReg();
+    if (isCalleeSavedReg(MI->getParent()->getParent(), RI, SReg))
+      OutStreamer.EmitWin64EHPushReg(RI->getSEHRegNum(SReg));
+    else
+      OutStreamer.EmitWin64EHAllocStack(8);
+    break;
+  case X86::SUB64ri8:
+  case X86::SUB64ri32: {
+    DReg = MI->getOperand(0).getReg();
+    Offset = MI->getOperand(2).getImm();
+    if (DReg == RI->getStackRegister()) {
+      OutStreamer.EmitWin64EHAllocStack(Offset);
+    }
+  } break;
+  case X86::MOV64rr:
+    DReg = MI->getOperand(0).getReg();
+    SReg = MI->getOperand(1).getReg();
+    if (DReg == RI->getFrameRegister(*MF) && SReg == RI->getStackRegister())
+      OutStreamer.EmitWin64EHSetFrame(RI->getSEHRegNum(DReg), 0);
+    break;
+  case X86::MOV64mr:
+  case X86::MOVAPSmr:
+    DReg = MI->getOperand(0).getReg();
+    Offset = MI->getOperand(0).getOffset();
+    SReg = MI->getOperand(1).getReg();
+    if (DReg == RI->getFrameRegister(*MF) || DReg == RI->getStackRegister()) {
+      if (MI->getOpcode() == X86::MOVAPSmr)
+        OutStreamer.EmitWin64EHSaveXMM(RI->getSEHRegNum(SReg), Offset);
+      else
+        OutStreamer.EmitWin64EHSaveReg(RI->getSEHRegNum(SReg), Offset);
+    }
+    break;
+  case X86::MOV64ri:
+    // Occurs before alloca()/call to __chkstk
+    assert((MI->getNextNode()
+            && MI->getNextNode()->getOpcode() == X86::W64ALLOCA
+            && MI->getOperand(0).getReg() == X86::RAX)
+           && "X86::MOV64ri only allowed before X86::W64ALLOCA");
+    break;
+  case X86::W64ALLOCA:
+    // Occurs if alloca()/call to __chkstk is used. Do nothing.
+    break;
+  case X86::SUB64rr:
+    // Occurs after alloca()/call to __chkstk
+    assert((MI->getPrevNode()
+            && MI->getPrevNode()->getOpcode() == X86::W64ALLOCA
+            && MI->getOperand(0).getReg() == RI->getStackRegister()
+            && MI->getOperand(2).getReg() == X86::RAX)
+           && "X86::SUB64rr only allowed after X86::W64ALLOCA");
+    break;
+  }
+  // Prolog ends if next instruction does not have the FrameSetup flag
+  // and is not the PROLOG_LABEL.
+  if (MI->getNextNode()) {
+    const MachineInstr *MI2 = MI->getNextNode();
+    if (!(MI2->getFlag(MachineInstr::FrameSetup) ||
+          (MI2->isPrologLabel() && MI2->getNextNode() &&
+           MI2->getNextNode()->getFlag(MachineInstr::FrameSetup)))) {
+      OutStreamer.EmitWin64EHEndProlog();
+    }
+  }
+}
+
 void X86AsmPrinter::EmitInstruction(const MachineInstr *MI) {
+  // Emit end of prolog for Win64-style EH if there is no framesetup.
+  if (MAI->getExceptionHandlingType() == ExceptionHandling::Win64 &&
+      !MI->getFlag(MachineInstr::FrameSetup) &&
+      MI->getParent()->getParent()->getFunction()->needsUnwindTableEntry() &&
+      MI == MI->getParent()->instr_begin() &&
+      MI->getParent()->getNumber() == 0) {
+    OutStreamer.EmitWin64EHEndProlog();
+  }
+
   X86MCInstLower MCInstLowering(*MF, *this);
   switch (MI->getOpcode()) {
   case TargetOpcode::DBG_VALUE:
@@ -965,4 +1058,11 @@ void X86AsmPrinter::EmitInstruction(const MachineInstr *MI) {
   MCInst TmpInst;
   MCInstLowering.Lower(MI, TmpInst);
   OutStreamer.EmitInstruction(TmpInst);
+
+  // Emit SEH unwind info for Win64-style EH.
+  if (MAI->getExceptionHandlingType() == ExceptionHandling::Win64 &&
+      MI->getFlag(MachineInstr::FrameSetup) &&
+      MI->getParent()->getParent()->getFunction()->needsUnwindTableEntry()) {
+    EmitUnwindingInstruction(MI);
+  }
 }
diff --git a/test/CodeGen/X86/avx-win64-args.ll b/test/CodeGen/X86/avx-win64-args.ll
index 85b2634..3b2f3d4 100644
--- a/test/CodeGen/X86/avx-win64-args.ll
+++ b/test/CodeGen/X86/avx-win64-args.ll
@@ -1,4 +1,6 @@
 ; RUN: llc < %s -mcpu=corei7-avx -mattr=+avx | FileCheck %s
+; XFAIL: *
+; See PR16779.
 target triple = "x86_64-pc-win32"
 
 declare <8 x float> @foo(<8 x float>, i32)
diff --git a/test/CodeGen/X86/win64_eh.ll b/test/CodeGen/X86/win64_eh.ll
new file mode 100644
index 0000000..4bc1dc2
--- /dev/null
+++ b/test/CodeGen/X86/win64_eh.ll
@@ -0,0 +1,125 @@
+; RUN: llc < %s -O0 -mtriple=x86_64-pc-win32 | FileCheck %s
+
+; Check function with nor prolog
+define void @foo0() uwtable {
+entry:
+  ret void
+}
+; CHECK: .seh_proc foo0
+; CHECK: .seh_endprologue
+; CHECK: ret
+; CHECK: .seh_endproc
+
+; Checks a small stack allocation
+define void @foo1() uwtable {
+entry:
+  %baz = alloca [2000 x i16], align 2
+  ret void
+}
+; CHECK: .seh_proc foo1
+; CHECK: subq $4000, %rsp
+; CHECK: .seh_stackalloc 4000
+; CHECK: .seh_endprologue
+; CHECK: ret
+; CHECK: .seh_endproc
+
+
+; Checks a stack allocation requiring call to __chkstk
+define void @foo2() uwtable {
+entry:
+  %baz = alloca [4000 x i16], align 2
+  ret void
+}
+; CHECK: .seh_proc foo2
+; CHECK: movabsq $8000, %rax
+; CHECK: callq __chkstk
+; CHECK: subq %rax, %rsp
+; CHECK: .seh_endprologue
+; CHECK: ret
+; CHECK: .seh_endproc
+
+
+; Checks stack push
+define i32 @foo3(i32 %f_arg, i32 %e_arg, i32 %d_arg, i32 %c_arg, i32 %b_arg, i32 %a_arg) uwtable {
+entry:
+  %a = alloca i32
+  %b = alloca i32
+  %c = alloca i32
+  %d = alloca i32
+  %e = alloca i32
+  %f = alloca i32
+  store i32 %a_arg, i32* %a
+  store i32 %b_arg, i32* %b
+  store i32 %c_arg, i32* %c
+  store i32 %d_arg, i32* %d
+  store i32 %e_arg, i32* %e
+  store i32 %f_arg, i32* %f
+  %tmp = load i32* %a
+  %tmp1 = mul i32 %tmp, 2
+  %tmp2 = load i32* %b
+  %tmp3 = mul i32 %tmp2, 3
+  %tmp4 = add i32 %tmp1, %tmp3
+  %tmp5 = load i32* %c
+  %tmp6 = mul i32 %tmp5, 5
+  %tmp7 = add i32 %tmp4, %tmp6
+  %tmp8 = load i32* %d
+  %tmp9 = mul i32 %tmp8, 7
+  %tmp10 = add i32 %tmp7, %tmp9
+  %tmp11 = load i32* %e
+  %tmp12 = mul i32 %tmp11, 11
+  %tmp13 = add i32 %tmp10, %tmp12
+  %tmp14 = load i32* %f
+  %tmp15 = mul i32 %tmp14, 13
+  %tmp16 = add i32 %tmp13, %tmp15
+  ret i32 %tmp16
+}
+; CHECK: .seh_proc foo3
+; CHECK: pushq %rsi
+; CHECK: .seh_pushreg 6
+; CHECK: subq $24, %rsp
+; CHECK: .seh_stackalloc 24
+; CHECK: .seh_endprologue
+; CHECK: ret
+; CHECK: .seh_endproc
+
+
+; Check emission of eh handler and handler data
+declare i32 @_d_eh_personality(i32, i32, i64, i8*, i8*)
+declare void @_d_eh_resume_unwind(i8*)
+
+declare i32 @bar()
+
+define i32 @foo4() #0 {
+entry:
+  %step = alloca i32, align 4
+  store i32 0, i32* %step
+  %tmp = load i32* %step
+
+  %tmp1 = invoke i32 @bar()
+          to label %finally unwind label %landingpad
+
+finally:
+  store i32 1, i32* %step
+  br label %endtryfinally
+
+landingpad:
+  %landing_pad = landingpad { i8*, i32 } personality i32 (i32, i32, i64, i8*, i8*)* @_d_eh_personality
+          cleanup
+  %tmp3 = extractvalue { i8*, i32 } %landing_pad, 0
+  store i32 2, i32* %step
+  call void @_d_eh_resume_unwind(i8* %tmp3)
+  unreachable
+
+endtryfinally:
+  %tmp10 = load i32* %step
+  ret i32 %tmp10
+}
+
+; CHECK: .seh_proc foo4
+; CHECK: .seh_handler _d_eh_personality, @unwind, @except
+; CHECK: subq $56, %rsp
+; CHECK: .seh_stackalloc 56
+; CHECK: .seh_endprologue
+; CHECK: ret
+; CHECK: .seh_handlerdata
+; CHECK: .seh_endproc
-- 
1.8.0.msysgit.0

-------------- next part --------------
Index: include/clang/Basic/LangOptions.def
===================================================================
--- include/clang/Basic/LangOptions.def	(Revision 194963)
+++ include/clang/Basic/LangOptions.def	(Arbeitskopie)
@@ -80,6 +80,7 @@
 LANGOPT(ObjCExceptions    , 1, 0, "Objective-C exceptions")
 LANGOPT(CXXExceptions     , 1, 0, "C++ exceptions")
 LANGOPT(SjLjExceptions    , 1, 0, "setjmp-longjump exception handling")
+LANGOPT(SEHExceptions     , 1, 0, "SEH exception handling")
 LANGOPT(TraditionalCPP    , 1, 0, "traditional CPP emulation")
 LANGOPT(RTTI              , 1, 1, "run-time type information")
 LANGOPT(MSBitfields       , 1, 0, "Microsoft-compatible structure layout")
Index: lib/CodeGen/CGException.cpp
===================================================================
--- lib/CodeGen/CGException.cpp	(Revision 194963)
+++ lib/CodeGen/CGException.cpp	(Arbeitskopie)
@@ -155,6 +155,7 @@
     static const EHPersonality &get(const LangOptions &Lang);
     static const EHPersonality GNU_C;
     static const EHPersonality GNU_C_SJLJ;
+    static const EHPersonality GNU_C_SEH;
     static const EHPersonality GNU_ObjC;
     static const EHPersonality GNUstep_ObjC;
     static const EHPersonality GNU_ObjCXX;
@@ -161,16 +162,20 @@
     static const EHPersonality NeXT_ObjC;
     static const EHPersonality GNU_CPlusPlus;
     static const EHPersonality GNU_CPlusPlus_SJLJ;
+    static const EHPersonality GNU_CPlusPlus_SEH;
   };
 }
 
 const EHPersonality EHPersonality::GNU_C = { "__gcc_personality_v0", 0 };
 const EHPersonality EHPersonality::GNU_C_SJLJ = { "__gcc_personality_sj0", 0 };
+const EHPersonality EHPersonality::GNU_C_SEH = { "__gcc_personality_seh0", 0 };
 const EHPersonality EHPersonality::NeXT_ObjC = { "__objc_personality_v0", 0 };
 const EHPersonality EHPersonality::GNU_CPlusPlus = { "__gxx_personality_v0", 0};
 const EHPersonality
 EHPersonality::GNU_CPlusPlus_SJLJ = { "__gxx_personality_sj0", 0 };
 const EHPersonality
+EHPersonality::GNU_CPlusPlus_SEH = { "__gxx_personality_seh0", 0 };
+const EHPersonality
 EHPersonality::GNU_ObjC = {"__gnu_objc_personality_v0", "objc_exception_throw"};
 const EHPersonality
 EHPersonality::GNU_ObjCXX = { "__gnustep_objcxx_personality_v0", 0 };
@@ -180,6 +185,8 @@
 static const EHPersonality &getCPersonality(const LangOptions &L) {
   if (L.SjLjExceptions)
     return EHPersonality::GNU_C_SJLJ;
+  if (L.SEHExceptions)
+    return EHPersonality::GNU_C_SEH;
   return EHPersonality::GNU_C;
 }
 
@@ -204,6 +211,8 @@
 static const EHPersonality &getCXXPersonality(const LangOptions &L) {
   if (L.SjLjExceptions)
     return EHPersonality::GNU_CPlusPlus_SJLJ;
+  else if (L.SEHExceptions)
+    return EHPersonality::GNU_CPlusPlus_SEH;
   else
     return EHPersonality::GNU_CPlusPlus;
 }


More information about the llvm-commits mailing list