diff --git a/clang/lib/CodeGen/CGCall.cpp b/clang/lib/CodeGen/CGCall.cpp --- a/clang/lib/CodeGen/CGCall.cpp +++ b/clang/lib/CodeGen/CGCall.cpp @@ -25,11 +25,13 @@ #include "clang/AST/DeclCXX.h" #include "clang/AST/DeclObjC.h" #include "clang/Basic/CodeGenOptions.h" +#include "clang/Basic/ObjCRuntime.h" #include "clang/Basic/TargetBuiltins.h" #include "clang/Basic/TargetInfo.h" #include "clang/CodeGen/CGFunctionInfo.h" #include "clang/CodeGen/SwiftCallingConv.h" #include "llvm/ADT/StringExtras.h" +#include "llvm/Analysis/ObjCARCInstKind.h" #include "llvm/Analysis/ValueTracking.h" #include "llvm/IR/Assumptions.h" #include "llvm/IR/Attributes.h" @@ -4465,16 +4467,37 @@ CodeGenFunction::getBundlesForFunclet(llvm::Value *Callee) { SmallVector BundleList; // There is no need for a funclet operand bundle if we aren't inside a - // funclet. + // funclet or the callee is not a function. if (!CurrentFuncletPad) return BundleList; - - // Skip intrinsics which cannot throw. auto *CalleeFn = dyn_cast(Callee->stripPointerCasts()); - if (CalleeFn && CalleeFn->isIntrinsic() && CalleeFn->doesNotThrow()) + if (!CalleeFn) return BundleList; - BundleList.emplace_back("funclet", CurrentFuncletPad); + // Skip intrinsics which cannot throw. + bool InsertFuncletOp = true; + if (CalleeFn->isIntrinsic() && CalleeFn->doesNotThrow()) + InsertFuncletOp = false; + + // Most ObjC ARC intrinics are lowered in PreISelIntrinsicLowering. Thus, + // WinEHPrepare will see them as regular calls. We need to set the funclet + // operand explicitly in this case to avoid accidental truncation of EH + // funclets on Windows. + if (CalleeFn->isIntrinsic() && CalleeFn->doesNotThrow()) { + if (CGM.getTarget().getTriple().isOSWindows()) { + assert(CGM.getLangOpts().ObjCRuntime.getKind() == ObjCRuntime::GNUstep && + "Only reproduced with GNUstep so far, but likely applies to other " + "ObjC runtimes on Windows"); + using namespace llvm::objcarc; + ARCInstKind CalleeKind = GetFunctionClass(CalleeFn); + if (!IsUser(CalleeKind) && CalleeKind != ARCInstKind::None) + InsertFuncletOp = true; + } + } + + if (InsertFuncletOp) + BundleList.emplace_back("funclet", CurrentFuncletPad); + return BundleList; } diff --git a/clang/test/CodeGenObjCXX/arc-exceptions-seh.mm b/clang/test/CodeGenObjCXX/arc-exceptions-seh.mm new file mode 100644 --- /dev/null +++ b/clang/test/CodeGenObjCXX/arc-exceptions-seh.mm @@ -0,0 +1,23 @@ +// RUN: %clang_cc1 -triple x86_64-pc-windows-msvc -emit-llvm -fobjc-arc -fexceptions -fobjc-exceptions -fobjc-runtime=gnustep-2.0 -o - %s | FileCheck %s + +@class Ety; +void opaque(void); +void test_catch_preisel_intrinsic(void) { + @try { + opaque(); + } @catch (Ety *ex) { + // Destroy ex when leaving catchpad. Emits calls to two intrinsic functions, + // that should both have a "funclet" bundle operand that refers to the + // catchpad's SSA value. + } +} + +// CHECK-LABEL: define{{.*}} void {{.*}}test_catch_preisel_intrinsic +// ... +// CHECK: catch.dispatch: +// CHECK-NEXT: [[CATCHSWITCH:%[0-9]+]] = catchswitch within none +// ... +// CHECK: catch: +// CHECK-NEXT: [[CATCHPAD:%[0-9]+]] = catchpad within [[CATCHSWITCH]] +// CHECK: {{%[0-9]+}} = call {{.*}} @llvm.objc.retain{{.*}} [ "funclet"(token [[CATCHPAD]]) ] +// CHECK: call {{.*}} @llvm.objc.storeStrong{{.*}} [ "funclet"(token [[CATCHPAD]]) ] diff --git a/llvm/lib/CodeGen/PreISelIntrinsicLowering.cpp b/llvm/lib/CodeGen/PreISelIntrinsicLowering.cpp --- a/llvm/lib/CodeGen/PreISelIntrinsicLowering.cpp +++ b/llvm/lib/CodeGen/PreISelIntrinsicLowering.cpp @@ -107,7 +107,9 @@ IRBuilder<> Builder(CI->getParent(), CI->getIterator()); SmallVector Args(CI->args()); - CallInst *NewCI = Builder.CreateCall(FCache, Args); + SmallVector BundleList; + CI->getOperandBundlesAsDefs(BundleList); + CallInst *NewCI = Builder.CreateCall(FCache, Args, BundleList); NewCI->setName(CI->getName()); // Try to set the most appropriate TailCallKind based on both the current diff --git a/llvm/lib/CodeGen/WinEHPrepare.cpp b/llvm/lib/CodeGen/WinEHPrepare.cpp --- a/llvm/lib/CodeGen/WinEHPrepare.cpp +++ b/llvm/lib/CodeGen/WinEHPrepare.cpp @@ -963,7 +963,7 @@ if (auto BU = CB->getOperandBundle(LLVMContext::OB_funclet)) FuncletBundleOperand = BU->Inputs.front(); - if (FuncletBundleOperand == FuncletPad) + if (!FuncletPad || FuncletBundleOperand == FuncletPad) continue; // Skip call sites which are nounwind intrinsics or inline asm. diff --git a/llvm/test/CodeGen/X86/win64-funclet-preisel-intrinsics.ll b/llvm/test/CodeGen/X86/win64-funclet-preisel-intrinsics.ll new file mode 100644 --- /dev/null +++ b/llvm/test/CodeGen/X86/win64-funclet-preisel-intrinsics.ll @@ -0,0 +1,67 @@ +; RUN: llc -mtriple=x86_64-windows-msvc < %s | FileCheck %s + +; Reduced IR generated from ObjC++ source: +; +; @class Ety; +; void opaque(void); +; void test_catch(void) { +; @try { +; opaque(); +; } @catch (Ety *ex) { +; // Destroy ex when leaving catchpad. This emits calls to two intrinsic +; // functions, llvm.objc.retain and llvm.objc.storeStrong, but only one +; // is required to trigger the funclet truncation. +; } +; } + +define void @test_catch() personality i8* bitcast (i32 (...)* @__CxxFrameHandler3 to i8*) { +entry: + %exn.slot = alloca i8*, align 8 + %ex2 = alloca i8*, align 8 + invoke void @opaque() to label %invoke.cont unwind label %catch.dispatch + +catch.dispatch: + %0 = catchswitch within none [label %catch] unwind to caller + +invoke.cont: + unreachable + +catch: + %1 = catchpad within %0 [i8* null, i32 64, i8** %exn.slot] + call void @llvm.objc.storeStrong(i8** %ex2, i8* null) [ "funclet"(token %1) ] + catchret from %1 to label %catchret.dest + +catchret.dest: + ret void +} + +declare void @opaque() +declare void @llvm.objc.storeStrong(i8**, i8*) #0 +declare i32 @__CxxFrameHandler3(...) + +attributes #0 = { nounwind } + +; llvm.objc.storeStrong is a Pre-ISel intrinsic, which used to cause truncations +; when it occurred in SEH funclets like catchpads: +; CHECK: # %catch +; CHECK: pushq %rbp +; CHECK: .seh_pushreg %rbp +; ... +; CHECK: .seh_endprologue +; +; At this point the code used to be truncated: +; CHECK-NOT: int3 +; +; Instead, the call to objc_storeStrong should be emitted: +; CHECK: leaq -24(%rbp), %rcx +; CHECK: xorl %edx, %edx +; CHECK: callq objc_storeStrong +; ... +; +; This is the end of the funclet: +; CHECK: popq %rbp +; CHECK: retq # CATCHRET +; ... +; CHECK: .seh_handlerdata +; ... +; CHECK: .seh_endproc