[ObjC][ARC] Teach the OptimizeSequences step of ObjCARCOpts about WinEH funclet tokens
authorStefan Gränitz <stefan.graenitz@gmail.com>
Tue, 24 Jan 2023 10:37:23 +0000 (11:37 +0100)
committerStefan Gränitz <stefan.graenitz@gmail.com>
Tue, 24 Jan 2023 14:17:18 +0000 (15:17 +0100)
When optimizing retain-release-sequences we insert (and delete) ObjC runtime calls. These calls need a funclet operand bundle that refers to the enclosing funclet pad whenever they are inserted in a WinEH funclet. WinEH funclets can contain multiple basic blocks. In order to find the enclosing funclet pad, we have to calculate the funclet coloring first.

Reviewed By: ahatanak

Differential Revision: https://reviews.llvm.org/D137944

clang/test/CodeGenObjCXX/arc-exceptions-seh.mm
llvm/lib/Transforms/ObjCARC/ObjCARCOpts.cpp
llvm/test/Transforms/ObjCARC/funclet-catchpad.ll [new file with mode: 0644]

index d5da111..0b697be 100644 (file)
@@ -1,5 +1,5 @@
-// RUN: %clang_cc1 -triple x86_64-pc-windows-msvc -emit-llvm -fobjc-arc -fexceptions -fobjc-exceptions -fobjc-arc-exceptions -fobjc-runtime=gnustep-2.0 -o - %s | FileCheck %s --check-prefixes=CHECK,CHECK-O0
-// RUN: %clang_cc1 -O2 -triple x86_64-pc-windows-msvc -emit-llvm -fobjc-arc -fexceptions -fobjc-exceptions -fobjc-arc-exceptions -fobjc-runtime=gnustep-2.0 -mllvm -enable-objc-arc-opts=false -o - %s | FileCheck %s --check-prefixes=CHECK,CHECK-O2
+// RUN: %clang_cc1 -O0 -triple x86_64-pc-windows-msvc -emit-llvm -fobjc-arc -fexceptions -fobjc-exceptions -fobjc-arc-exceptions -fobjc-runtime=gnustep-2.0 -o - %s | FileCheck %s --check-prefixes=CHECK,CHECK-O0
+// RUN: %clang_cc1 -O2 -triple x86_64-pc-windows-msvc -emit-llvm -fobjc-arc -fexceptions -fobjc-exceptions -fobjc-arc-exceptions -fobjc-runtime=gnustep-2.0 -o - %s | FileCheck %s --check-prefixes=CHECK,CHECK-O2
 
 // WinEH requires funclet tokens on nounwind intrinsics if they can lower to
 // regular function calls in the course of IR transformations.
@@ -24,7 +24,9 @@ void try_catch_with_objc_intrinsic() {
 // CHECK-LABEL:   try_catch_with_objc_intrinsic
 //
 // CHECK:         catch.dispatch:
-// CHECK-NEXT:      [[CATCHSWITCH:%[0-9]+]] = catchswitch within none [label %catch] unwind label %[[CLEANUP1:.*]]
+// CHECK-NEXT:      [[CATCHSWITCH:%[0-9]+]] = catchswitch within none [label %catch]
+// CHECK-O0:          unwind label %[[CLEANUP1:.*]]
+// CHECK-O2:          unwind to caller
 //
 // All calls within a catchpad must have funclet tokens that refer to it:
 // CHECK:         catch:
@@ -58,12 +60,12 @@ void try_catch_with_objc_intrinsic() {
 // CHECK-O2:          @llvm.objc.release
 // CHECK:             [ "funclet"(token [[CLEANUPPAD2]]) ]
 // CHECK:           cleanupret from [[CLEANUPPAD2]]
-// CHECK:             unwind label %[[CLEANUP1]]
+// CHECK-O0:          unwind label %[[CLEANUP1]]
+// CHECK-O2:          unwind to caller
 //
-// CHECK:         [[CLEANUP1]]:
-// CHECK-NEXT:      [[CLEANUPPAD1:%[0-9]+]] = cleanuppad within none
-// CHECK:           call
+// CHECK-O0:      [[CLEANUP1]]:
+// CHECK-O0-NEXT:   [[CLEANUPPAD1:%[0-9]+]] = cleanuppad within none
+// CHECK-O0:        call
 // CHECK-O0:          @llvm.objc.storeStrong
-// CHECK-O2:          @llvm.objc.release
-// CHECK:             [ "funclet"(token [[CLEANUPPAD1]]) ]
-// CHECK:           cleanupret from [[CLEANUPPAD1]] unwind to caller
+// CHECK-O0:          [ "funclet"(token [[CLEANUPPAD1]]) ]
+// CHECK-O0:        cleanupret from [[CLEANUPPAD1]] unwind to caller
index 927f389..5108598 100644 (file)
@@ -501,6 +501,8 @@ class ObjCARCOpt {
   /// is in fact used in the current function.
   unsigned UsedInThisFunction;
 
+  DenseMap<BasicBlock *, ColorVector> BlockEHColors;
+
   bool OptimizeRetainRVCall(Function &F, Instruction *RetainRV);
   void OptimizeAutoreleaseRVCall(Function &F, Instruction *AutoreleaseRV,
                                  ARCInstKind &Class);
@@ -508,17 +510,16 @@ class ObjCARCOpt {
 
   /// Optimize an individual call, optionally passing the
   /// GetArgRCIdentityRoot if it has already been computed.
-  void OptimizeIndividualCallImpl(
-      Function &F, DenseMap<BasicBlock *, ColorVector> &BlockColors,
-      Instruction *Inst, ARCInstKind Class, const Value *Arg);
+  void OptimizeIndividualCallImpl(Function &F, Instruction *Inst,
+                                  ARCInstKind Class, const Value *Arg);
 
   /// Try to optimize an AutoreleaseRV with a RetainRV or UnsafeClaimRV.  If the
   /// optimization occurs, returns true to indicate that the caller should
   /// assume the instructions are dead.
-  bool OptimizeInlinedAutoreleaseRVCall(
-      Function &F, DenseMap<BasicBlock *, ColorVector> &BlockColors,
-      Instruction *Inst, const Value *&Arg, ARCInstKind Class,
-      Instruction *AutoreleaseRV, const Value *&AutoreleaseRVArg);
+  bool OptimizeInlinedAutoreleaseRVCall(Function &F, Instruction *Inst,
+                                        const Value *&Arg, ARCInstKind Class,
+                                        Instruction *AutoreleaseRV,
+                                        const Value *&AutoreleaseRVArg);
 
   void CheckForCFGHazards(const BasicBlock *BB,
                           DenseMap<const BasicBlock *, BBState> &BBStates,
@@ -566,12 +567,46 @@ class ObjCARCOpt {
 
   void OptimizeReturns(Function &F);
 
+  Instruction *cloneCallInstForBB(CallInst &CI, BasicBlock &BB) {
+    SmallVector<OperandBundleDef, 1> OpBundles;
+    for (unsigned I = 0, E = CI.getNumOperandBundles(); I != E; ++I) {
+      auto Bundle = CI.getOperandBundleAt(I);
+      // Funclets will be reassociated in the future.
+      if (Bundle.getTagID() == LLVMContext::OB_funclet)
+        continue;
+      OpBundles.emplace_back(Bundle);
+    }
+
+    if (!BlockEHColors.empty()) {
+      const ColorVector &CV = BlockEHColors.find(&BB)->second;
+      assert(CV.size() > 0 && "non-unique color for block!");
+      Instruction *EHPad = CV.front()->getFirstNonPHI();
+      if (EHPad->isEHPad())
+        OpBundles.emplace_back("funclet", EHPad);
+    }
+
+    return CallInst::Create(&CI, OpBundles);
+  }
+
+  void addOpBundleForFunclet(BasicBlock *BB,
+                             SmallVectorImpl<OperandBundleDef> &OpBundles) {
+    if (!BlockEHColors.empty()) {
+      const ColorVector &CV = BlockEHColors.find(BB)->second;
+      assert(CV.size() > 0 && "Uncolored block");
+      for (BasicBlock *EHPadBB : CV)
+        if (auto *EHPad = dyn_cast<FuncletPadInst>(EHPadBB->getFirstNonPHI())) {
+          OpBundles.emplace_back("funclet", EHPad);
+          return;
+        }
+    }
+  }
+
 #ifndef NDEBUG
   void GatherStatistics(Function &F, bool AfterOptimization = false);
 #endif
 
   public:
-    void init(Module &M);
+    void init(Function &F);
     bool run(Function &F, AAResults &AA);
     bool hasCFGChanged() const { return CFGChanged; }
 };
@@ -624,8 +659,7 @@ ObjCARCOpt::OptimizeRetainRVCall(Function &F, Instruction *RetainRV) {
 }
 
 bool ObjCARCOpt::OptimizeInlinedAutoreleaseRVCall(
-    Function &F, DenseMap<BasicBlock *, ColorVector> &BlockColors,
-    Instruction *Inst, const Value *&Arg, ARCInstKind Class,
+    Function &F, Instruction *Inst, const Value *&Arg, ARCInstKind Class,
     Instruction *AutoreleaseRV, const Value *&AutoreleaseRVArg) {
   if (BundledInsts->contains(Inst))
     return false;
@@ -678,8 +712,7 @@ bool ObjCARCOpt::OptimizeInlinedAutoreleaseRVCall(
   EraseInstruction(Inst);
 
   // Run the normal optimizations on Release.
-  OptimizeIndividualCallImpl(F, BlockColors, Release, ARCInstKind::Release,
-                             Arg);
+  OptimizeIndividualCallImpl(F, Release, ARCInstKind::Release, Arg);
   return true;
 }
 
@@ -732,31 +765,6 @@ void ObjCARCOpt::OptimizeAutoreleaseRVCall(Function &F,
   LLVM_DEBUG(dbgs() << "New: " << *AutoreleaseRV << "\n");
 }
 
-namespace {
-Instruction *
-CloneCallInstForBB(CallInst &CI, BasicBlock &BB,
-                   const DenseMap<BasicBlock *, ColorVector> &BlockColors) {
-  SmallVector<OperandBundleDef, 1> OpBundles;
-  for (unsigned I = 0, E = CI.getNumOperandBundles(); I != E; ++I) {
-    auto Bundle = CI.getOperandBundleAt(I);
-    // Funclets will be reassociated in the future.
-    if (Bundle.getTagID() == LLVMContext::OB_funclet)
-      continue;
-    OpBundles.emplace_back(Bundle);
-  }
-
-  if (!BlockColors.empty()) {
-    const ColorVector &CV = BlockColors.find(&BB)->second;
-    assert(CV.size() == 1 && "non-unique color for block!");
-    Instruction *EHPad = CV.front()->getFirstNonPHI();
-    if (EHPad->isEHPad())
-      OpBundles.emplace_back("funclet", EHPad);
-  }
-
-  return CallInst::Create(&CI, OpBundles);
-}
-}
-
 /// Visit each call, one at a time, and make simplifications without doing any
 /// additional analysis.
 void ObjCARCOpt::OptimizeIndividualCalls(Function &F) {
@@ -764,11 +772,6 @@ void ObjCARCOpt::OptimizeIndividualCalls(Function &F) {
   // Reset all the flags in preparation for recomputing them.
   UsedInThisFunction = 0;
 
-  DenseMap<BasicBlock *, ColorVector> BlockColors;
-  if (F.hasPersonalityFn() &&
-      isScopedEHPersonality(classifyEHPersonality(F.getPersonalityFn())))
-    BlockColors = colorEHFunclets(F);
-
   // Store any delayed AutoreleaseRV intrinsics, so they can be easily paired
   // with RetainRV and UnsafeClaimRV.
   Instruction *DelayedAutoreleaseRV = nullptr;
@@ -781,7 +784,7 @@ void ObjCARCOpt::OptimizeIndividualCalls(Function &F) {
   auto optimizeDelayedAutoreleaseRV = [&]() {
     if (!DelayedAutoreleaseRV)
       return;
-    OptimizeIndividualCallImpl(F, BlockColors, DelayedAutoreleaseRV,
+    OptimizeIndividualCallImpl(F, DelayedAutoreleaseRV,
                                ARCInstKind::AutoreleaseRV,
                                DelayedAutoreleaseRVArg);
     setDelayedAutoreleaseRV(nullptr);
@@ -844,7 +847,7 @@ void ObjCARCOpt::OptimizeIndividualCalls(Function &F) {
     case ARCInstKind::UnsafeClaimRV:
       if (DelayedAutoreleaseRV) {
         // We have a potential RV pair.  Check if they cancel out.
-        if (OptimizeInlinedAutoreleaseRVCall(F, BlockColors, Inst, Arg, Class,
+        if (OptimizeInlinedAutoreleaseRVCall(F, Inst, Arg, Class,
                                              DelayedAutoreleaseRV,
                                              DelayedAutoreleaseRVArg)) {
           setDelayedAutoreleaseRV(nullptr);
@@ -855,7 +858,7 @@ void ObjCARCOpt::OptimizeIndividualCalls(Function &F) {
       break;
     }
 
-    OptimizeIndividualCallImpl(F, BlockColors, Inst, Class, Arg);
+    OptimizeIndividualCallImpl(F, Inst, Class, Arg);
   }
 
   // Catch the final delayed AutoreleaseRV.
@@ -889,9 +892,9 @@ static bool isInertARCValue(Value *V, SmallPtrSet<Value *, 1> &VisitedPhis) {
   return false;
 }
 
-void ObjCARCOpt::OptimizeIndividualCallImpl(
-    Function &F, DenseMap<BasicBlock *, ColorVector> &BlockColors,
-    Instruction *Inst, ARCInstKind Class, const Value *Arg) {
+void ObjCARCOpt::OptimizeIndividualCallImpl(Function &F, Instruction *Inst,
+                                            ARCInstKind Class,
+                                            const Value *Arg) {
   LLVM_DEBUG(dbgs() << "Visiting: Class: " << Class << "; " << *Inst << "\n");
 
   // We can delete this call if it takes an inert value.
@@ -1149,8 +1152,8 @@ void ObjCARCOpt::OptimizeIndividualCallImpl(
         continue;
       Value *Op = PN->getIncomingValue(i);
       Instruction *InsertPos = &PN->getIncomingBlock(i)->back();
-      CallInst *Clone = cast<CallInst>(
-          CloneCallInstForBB(*CInst, *InsertPos->getParent(), BlockColors));
+      CallInst *Clone =
+          cast<CallInst>(cloneCallInstForBB(*CInst, *InsertPos->getParent()));
       if (Op->getType() != ParamTy)
         Op = new BitCastInst(Op, ParamTy, "", InsertPos);
       Clone->setArgOperand(0, Op);
@@ -1772,7 +1775,9 @@ void ObjCARCOpt::MoveCalls(Value *Arg, RRInfo &RetainsToMove,
     Value *MyArg = ArgTy == ParamTy ? Arg :
                    new BitCastInst(Arg, ParamTy, "", InsertPt);
     Function *Decl = EP.get(ARCRuntimeEntryPointKind::Retain);
-    CallInst *Call = CallInst::Create(Decl, MyArg, "", InsertPt);
+    SmallVector<OperandBundleDef, 1> BundleList;
+    addOpBundleForFunclet(InsertPt->getParent(), BundleList);
+    CallInst *Call = CallInst::Create(Decl, MyArg, BundleList, "", InsertPt);
     Call->setDoesNotThrow();
     Call->setTailCall();
 
@@ -1785,7 +1790,9 @@ void ObjCARCOpt::MoveCalls(Value *Arg, RRInfo &RetainsToMove,
     Value *MyArg = ArgTy == ParamTy ? Arg :
                    new BitCastInst(Arg, ParamTy, "", InsertPt);
     Function *Decl = EP.get(ARCRuntimeEntryPointKind::Release);
-    CallInst *Call = CallInst::Create(Decl, MyArg, "", InsertPt);
+    SmallVector<OperandBundleDef, 1> BundleList;
+    addOpBundleForFunclet(InsertPt->getParent(), BundleList);
+    CallInst *Call = CallInst::Create(Decl, MyArg, BundleList, "", InsertPt);
     // Attach a clang.imprecise_release metadata tag, if appropriate.
     if (MDNode *M = ReleasesToMove.ReleaseMetadata)
       Call->setMetadata(MDKindCache.get(ARCMDKindID::ImpreciseRelease), M);
@@ -2401,17 +2408,22 @@ ObjCARCOpt::GatherStatistics(Function &F, bool AfterOptimization) {
 }
 #endif
 
-void ObjCARCOpt::init(Module &M) {
+void ObjCARCOpt::init(Function &F) {
   if (!EnableARCOpts)
     return;
 
   // Intuitively, objc_retain and others are nocapture, however in practice
   // they are not, because they return their argument value. And objc_release
   // calls finalizers which can have arbitrary side effects.
-  MDKindCache.init(&M);
+  MDKindCache.init(F.getParent());
 
   // Initialize our runtime entry point cache.
-  EP.init(&M);
+  EP.init(F.getParent());
+
+  // Compute which blocks are in which funclet.
+  if (F.hasPersonalityFn() &&
+      isScopedEHPersonality(classifyEHPersonality(F.getPersonalityFn())))
+    BlockEHColors = colorEHFunclets(F);
 }
 
 bool ObjCARCOpt::run(Function &F, AAResults &AA) {
@@ -2487,7 +2499,7 @@ bool ObjCARCOpt::run(Function &F, AAResults &AA) {
 PreservedAnalyses ObjCARCOptPass::run(Function &F,
                                       FunctionAnalysisManager &AM) {
   ObjCARCOpt OCAO;
-  OCAO.init(*F.getParent());
+  OCAO.init(F);
 
   bool Changed = OCAO.run(F, AM.getResult<AAManager>(F));
   bool CFGChanged = OCAO.hasCFGChanged();
diff --git a/llvm/test/Transforms/ObjCARC/funclet-catchpad.ll b/llvm/test/Transforms/ObjCARC/funclet-catchpad.ll
new file mode 100644 (file)
index 0000000..9f047c2
--- /dev/null
@@ -0,0 +1,40 @@
+; RUN: opt -mtriple=x86_64-windows-msvc -passes=objc-arc -S < %s | FileCheck %s
+
+; Check that funclet tokens are preserved
+;
+; CHECK-LABEL:  catch:
+; CHECK:          %1 = catchpad within %0
+; CHECK:          %2 = tail call ptr @llvm.objc.retain(ptr %exn) #0 [ "funclet"(token %1) ]
+; CHECK:          call void @llvm.objc.release(ptr %exn) #0 [ "funclet"(token %1) ]
+; CHECK:          catchret from %1 to label %eh.cont
+
+define void @try_catch_with_objc_intrinsic() personality ptr @__CxxFrameHandler3 {
+entry:
+  %exn.slot = alloca ptr, align 8
+  invoke void @may_throw(ptr null) to label %eh.cont unwind label %catch.dispatch
+
+catch.dispatch:                                   ; preds = %entry
+  %0 = catchswitch within none [label %catch] unwind to caller
+
+eh.cont:                                          ; preds = %catch, %entry
+  ret void
+
+catch:                                            ; preds = %catch.dispatch
+  %1 = catchpad within %0 [ptr null, i32 0, ptr %exn.slot]
+  br label %if.then
+
+if.then:                                          ; preds = %catch
+  %exn = load ptr, ptr null, align 8
+  %2 = call ptr @llvm.objc.retain(ptr %exn) [ "funclet"(token %1) ]
+  call void @may_throw(ptr %exn)
+  call void @llvm.objc.release(ptr %exn) [ "funclet"(token %1) ]
+  catchret from %1 to label %eh.cont
+}
+
+declare void @may_throw(ptr)
+declare i32 @__CxxFrameHandler3(...)
+
+declare ptr @llvm.objc.retain(ptr) #0
+declare void @llvm.objc.release(ptr) #0
+
+attributes #0 = { nounwind }