[WinEH] Emit int3 after noreturn calls on Win64
authorReid Kleckner <rnk@google.com>
Wed, 30 Sep 2015 23:09:23 +0000 (23:09 +0000)
committerReid Kleckner <rnk@google.com>
Wed, 30 Sep 2015 23:09:23 +0000 (23:09 +0000)
The Win64 unwinder disassembles forwards from each PC to try to
determine if this PC is in an epilogue. If so, it skips calling the EH
personality function for that frame. Typically, this means you cannot
catch an exception in the same frame that you threw it, because 'throw'
calls a noreturn runtime function.

Previously we avoided this problem with the TrapUnreachable
TargetOption, but that's a much bigger hammer than we need. All we need
is a 1 byte non-epilogue instruction right after the call.  Instead,
what we got was an unconditional branch to a shared block containing the
ud2, potentially 7 bytes instead of 1. So, this reverts r206684, which
added TrapUnreachable, and replaces it with something better.

The new code pattern matches for invoke/call followed by unreachable and
inserts an int3 into the DAG. To be 100% watertight, we would need to
insert SEH_Epilogue instructions into all basic blocks ending in a call
with no terminators or successors, but in practice this is unlikely to
come up.

git-svn-id: https://llvm.org/svn/llvm-project/llvm/trunk@248959 91177308-0d34-0410-b5e6-96231b3b80d8

include/llvm/Target/TargetOptions.h
lib/CodeGen/SelectionDAG/FastISel.cpp
lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp
lib/Target/X86/X86ISelLowering.cpp
lib/Target/X86/X86TargetMachine.cpp
test/CodeGen/X86/br-fold.ll
test/CodeGen/X86/win-catchpad-rethrow.ll [new file with mode: 0644]
test/CodeGen/X86/win64_call_epi.ll

index bd696f399d54358e69b9fa9130de5cd8a51e3a64..e931ec875c0111ebdb9575ff1d98df834d904bfe 100644 (file)
@@ -71,7 +71,7 @@ namespace llvm {
           EnableFastISel(false), PositionIndependentExecutable(false),
           UseInitArray(false), DisableIntegratedAS(false),
           CompressDebugSections(false), FunctionSections(false),
-          DataSections(false), UniqueSectionNames(true), TrapUnreachable(false),
+          DataSections(false), UniqueSectionNames(true),
           EmulatedTLS(false), FloatABIType(FloatABI::Default),
           AllowFPOpFusion(FPOpFusion::Standard), Reciprocals(TargetRecip()),
           JTType(JumpTable::Single),
@@ -169,9 +169,6 @@ namespace llvm {
 
     unsigned UniqueSectionNames : 1;
 
-    /// Emit target-specific trap instruction for 'unreachable' IR instructions.
-    unsigned TrapUnreachable : 1;
-
     /// EmulatedTLS - This flag enables emulated TLS model, using emutls
     /// function in the runtime library..
     unsigned EmulatedTLS : 1;
@@ -234,7 +231,6 @@ inline bool operator==(const TargetOptions &LHS,
     ARE_EQUAL(EnableFastISel) &&
     ARE_EQUAL(PositionIndependentExecutable) &&
     ARE_EQUAL(UseInitArray) &&
-    ARE_EQUAL(TrapUnreachable) &&
     ARE_EQUAL(EmulatedTLS) &&
     ARE_EQUAL(FloatABIType) &&
     ARE_EQUAL(AllowFPOpFusion) &&
index 13b097cfc6065e5329a458225dce007ee182eda6..f97edb4ef0904a2939cba7754ca924df68087e98 100644 (file)
@@ -1569,10 +1569,8 @@ bool FastISel::selectOperator(const User *I, unsigned Opcode) {
   }
 
   case Instruction::Unreachable:
-    if (TM.Options.TrapUnreachable)
-      return fastEmit_(MVT::Other, MVT::Other, ISD::TRAP) != 0;
-    else
-      return true;
+    // Nothing to emit.
+    return true;
 
   case Instruction::Alloca:
     // FunctionLowering has the static-sized case covered.
index 72af2760bb7dfb4c5915230d40e082cb6bc3aedb..a48de572113750efe9a1c2804b4f92236064b2df 100644 (file)
@@ -2205,10 +2205,7 @@ void SelectionDAGBuilder::visitIndirectBr(const IndirectBrInst &I) {
                           getValue(I.getAddress())));
 }
 
-void SelectionDAGBuilder::visitUnreachable(const UnreachableInst &I) {
-  if (DAG.getTarget().Options.TrapUnreachable)
-    DAG.setRoot(DAG.getNode(ISD::TRAP, getCurSDLoc(), MVT::Other, DAG.getRoot()));
-}
+void SelectionDAGBuilder::visitUnreachable(const UnreachableInst &I) {}
 
 void SelectionDAGBuilder::visitFSub(const User &I) {
   // -0.0 - X --> fneg
index 6fd04733e01dce21372cf0b17700320b3851250d..0ed196a4acb5f55b92fafcd47f83653e11c86b84 100644 (file)
@@ -2945,6 +2945,20 @@ static SDValue getMOVL(SelectionDAG &DAG, SDLoc dl, EVT VT, SDValue V1,
   return DAG.getVectorShuffle(VT, dl, V1, V2, &Mask[0]);
 }
 
+/// Check if the fall through instruction after a call site is unreachable.
+/// FIXME: This will fail if there are interesting non-code generating IR
+/// instructions between the call and the unreachable (lifetime.end). In
+/// practice, this should be rare because optimizations like to delete non-call
+/// code before unreachable.
+static bool isCallFollowedByUnreachable(ImmutableCallSite CS) {
+  const Instruction *NextInst;
+  if (auto *II = dyn_cast<InvokeInst>(CS.getInstruction()))
+    NextInst = II->getNormalDest()->getFirstNonPHIOrDbg();
+  else
+    NextInst = CS.getInstruction()->getNextNode();
+  return isa<UnreachableInst>(NextInst);
+}
+
 SDValue
 X86TargetLowering::LowerCall(TargetLowering::CallLoweringInfo &CLI,
                              SmallVectorImpl<SDValue> &InVals) const {
@@ -3450,6 +3464,15 @@ X86TargetLowering::LowerCall(TargetLowering::CallLoweringInfo &CLI,
     InFlag = Chain.getValue(1);
   }
 
+  if (Subtarget->isTargetWin64() && CLI.CS) {
+    // Look for a call followed by unreachable. On Win64, we need to ensure that
+    // the call does not accidentally fall through to something that looks like
+    // an epilogue. We do this by inserting a DEBUGTRAP, which lowers to int3,
+    // which is what MSVC emits after noreturn calls.
+    if (isCallFollowedByUnreachable(*CLI.CS))
+      Chain = DAG.getNode(ISD::DEBUGTRAP, dl, MVT::Other, Chain);
+  }
+
   // Handle result values, copying them out of physregs into vregs that we
   // return.
   return LowerCallResult(Chain, InFlag, CallConv, isVarArg,
index 2e869eb7c3cd6eaf34b0d2b68d8cbae06f5ecab0..d318244e0f70710ac78b5e32b9eaad3adf36fd19 100644 (file)
@@ -110,13 +110,6 @@ X86TargetMachine::X86TargetMachine(const Target &T, const Triple &TT,
                         OL),
       TLOF(createTLOF(getTargetTriple())),
       Subtarget(TT, CPU, FS, *this, Options.StackAlignmentOverride) {
-  // Windows stack unwinder gets confused when execution flow "falls through"
-  // after a call to 'noreturn' function.
-  // To prevent that, we emit a trap for 'unreachable' IR instructions.
-  // (which on X86, happens to be the 'ud2' instruction)
-  if (Subtarget.isTargetWin64())
-    this->Options.TrapUnreachable = true;
-
   // By default (and when -ffast-math is on), enable estimate codegen for
   // everything except scalar division. By default, use 1 refinement step for
   // all operations. Defaults may be overridden by using command-line options.
index fd1e73bde8cc5d457fbba5676958e52b3efb7c38..9a7552ddad82a2c70abb1f10d5f72c5856b38038 100644 (file)
 ; X64_LINUX-NEXT: %bb8.i329
 
 ; X64_WINDOWS: orq %rax, %rcx
-; X64_WINDOWS-NEXT: ud2
+; X64_WINDOWS-NEXT: %bb8.i329
 
 ; X64_WINDOWS_GNU: orq %rax, %rcx
-; X64_WINDOWS_GNU-NEXT: ud2
+; X64_WINDOWS_GNU-NEXT: %bb8.i329
 
 @_ZN11xercesc_2_513SchemaSymbols21fgURI_SCHEMAFORSCHEMAE = external constant [33 x i16], align 32 ; <[33 x i16]*> [#uses=1]
 @_ZN11xercesc_2_56XMLUni16fgNotationStringE = external constant [9 x i16], align 16 ; <[9 x i16]*> [#uses=1]
diff --git a/test/CodeGen/X86/win-catchpad-rethrow.ll b/test/CodeGen/X86/win-catchpad-rethrow.ll
new file mode 100644 (file)
index 0000000..7585b07
--- /dev/null
@@ -0,0 +1,103 @@
+; RUN: llc -mtriple=x86_64-pc-windows-msvc < %s | FileCheck %s
+
+; C++ EH rethrows are interesting, because they are calls to noreturn
+; functions. There *must* be some code after the call instruction that doesn't
+; look like an epilogue. We use int3 to be consistent with MSVC.
+
+; Based on this C++ source:
+; int main() {
+;   try {
+;     throw 42;
+;   } catch (int) {
+;     try {
+;       throw;
+;     } catch (int) {
+;     }
+;   }
+;   return 0;
+; }
+
+; ModuleID = 't.cpp'
+target datalayout = "e-m:w-i64:64-f80:128-n8:16:32:64-S128"
+target triple = "x86_64-pc-windows-msvc"
+
+%rtti.TypeDescriptor2 = type { i8**, i8*, [3 x i8] }
+%eh.CatchableType = type { i32, i32, i32, i32, i32, i32, i32 }
+%eh.CatchableTypeArray.1 = type { i32, [1 x i32] }
+%eh.ThrowInfo = type { i32, i32, i32, i32 }
+
+$"\01??_R0H@8" = comdat any
+
+$"_CT??_R0H@84" = comdat any
+
+$_CTA1H = comdat any
+
+$_TI1H = comdat any
+
+@"\01??_7type_info@@6B@" = external constant i8*
+@"\01??_R0H@8" = linkonce_odr global %rtti.TypeDescriptor2 { i8** @"\01??_7type_info@@6B@", i8* null, [3 x i8] c".H\00" }, comdat
+@__ImageBase = external constant i8
+@"_CT??_R0H@84" = linkonce_odr unnamed_addr constant %eh.CatchableType { i32 1, i32 trunc (i64 sub nuw nsw (i64 ptrtoint (%rtti.TypeDescriptor2* @"\01??_R0H@8" to i64), i64 ptrtoint (i8* @__ImageBase to i64)) to i32), i32 0, i32 -1, i32 0, i32 4, i32 0 }, section ".xdata", comdat
+@_CTA1H = linkonce_odr unnamed_addr constant %eh.CatchableTypeArray.1 { i32 1, [1 x i32] [i32 trunc (i64 sub nuw nsw (i64 ptrtoint (%eh.CatchableType* @"_CT??_R0H@84" to i64), i64 ptrtoint (i8* @__ImageBase to i64)) to i32)] }, section ".xdata", comdat
+@_TI1H = linkonce_odr unnamed_addr constant %eh.ThrowInfo { i32 0, i32 0, i32 0, i32 trunc (i64 sub nuw nsw (i64 ptrtoint (%eh.CatchableTypeArray.1* @_CTA1H to i64), i64 ptrtoint (i8* @__ImageBase to i64)) to i32) }, section ".xdata", comdat
+
+define i32 @main() #0 personality i8* bitcast (i32 (...)* @__CxxFrameHandler3 to i8*) {
+entry:
+  %tmp = alloca i32, align 4
+  store i32 42, i32* %tmp, align 4
+  %0 = bitcast i32* %tmp to i8*
+  invoke void @_CxxThrowException(i8* %0, %eh.ThrowInfo* nonnull @_TI1H) #1
+          to label %unreachable unwind label %catch.dispatch
+
+catch.dispatch:                                   ; preds = %entry
+  %1 = catchpad [%rtti.TypeDescriptor2* @"\01??_R0H@8", i32 0, i8* null]
+          to label %catch unwind label %catchendblock
+
+catch:                                            ; preds = %catch.dispatch
+  invoke void @_CxxThrowException(i8* null, %eh.ThrowInfo* null) #1
+          to label %unreachable unwind label %catch.dispatch.1
+
+catch.dispatch.1:                                 ; preds = %catch
+  %2 = catchpad [%rtti.TypeDescriptor2* @"\01??_R0H@8", i32 0, i8* null]
+          to label %catch.3 unwind label %catchendblock.2
+
+catch.3:                                          ; preds = %catch.dispatch.1
+  catchret %2 to label %try.cont
+
+try.cont:                                         ; preds = %catch.3
+  catchret %1 to label %try.cont.5
+
+try.cont.5:                                       ; preds = %try.cont
+  ret i32 0
+
+catchendblock.2:                                  ; preds = %catch.dispatch.1
+  catchendpad unwind label %catchendblock
+
+catchendblock:                                    ; preds = %catchendblock.2, %catch.dispatch
+  catchendpad unwind to caller
+
+unreachable:                                      ; preds = %catch, %entry
+  unreachable
+}
+
+declare void @_CxxThrowException(i8*, %eh.ThrowInfo*)
+
+declare i32 @__CxxFrameHandler3(...)
+
+attributes #0 = { "disable-tail-calls"="false" "less-precise-fpmad"="false" "no-frame-pointer-elim"="false" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "stack-protector-buffer-size"="8" "target-features"="+sse,+sse2" "unsafe-fp-math"="false" "use-soft-float"="false" }
+attributes #1 = { noreturn }
+
+; CHECK: main:
+; CHECK: .seh_proc main
+; CHECK: movl $42,
+; CHECK-DAG: leaq {{.*}}, %rcx
+; CHECK-DAG: leaq _TI1H(%rip), %rdx
+; CHECK: callq _CxxThrowException
+; CHECK-NEXT: int3
+
+; CHECK: "?catch$1@?0?main@4HA":
+; CHECK: .seh_proc "?catch$1@?0?main@4HA"
+; CHECK-DAG: xorl %ecx, %ecx
+; CHECK-DAG: xorl %edx, %edx
+; CHECK: callq _CxxThrowException
+; CHECK-NEXT: int3
index 096cbe41c5404431ac903bbbefb262e14f5fe5a6..d514032dcbf84bbc0d1dd564202fc174a1e15e90 100644 (file)
@@ -24,9 +24,9 @@ catch:
 ; WIN64: nop
 ; WIN64: addq ${{[0-9]+}}, %rsp
 ; WIN64: retq
-; Check for 'ud2' after noreturn call
+; Check for 'int3' after noreturn call
 ; WIN64: callq _Unwind_Resume
-; WIN64-NEXT: ud2
+; WIN64-NEXT: int3
 ; WIN64: .seh_endproc