https://github.com/AZero13 updated https://github.com/llvm/llvm-project/pull/144788
>From e63c64bb71e99e822557b2d80b26d21648e3e0ec Mon Sep 17 00:00:00 2001 From: Rose <gfunni...@gmail.com> Date: Wed, 18 Jun 2025 16:05:44 -0400 Subject: [PATCH 1/2] [ObjCARC] Delete empty autoreleasepools with no autoreleases in them Erase empty autorelease pools that have no autorelease in them --- .../ObjCARC/ARCRuntimeEntryPoints.h | 16 +++ llvm/lib/Transforms/ObjCARC/ObjCARCOpts.cpp | 123 +++++++++++++++++- .../ObjCARC/test_autorelease_pool.ll | 118 +++++++++++++++++ 3 files changed, 252 insertions(+), 5 deletions(-) create mode 100644 llvm/test/Transforms/ObjCARC/test_autorelease_pool.ll diff --git a/llvm/lib/Transforms/ObjCARC/ARCRuntimeEntryPoints.h b/llvm/lib/Transforms/ObjCARC/ARCRuntimeEntryPoints.h index 3fa844eda21cf..6135c7b938a3e 100644 --- a/llvm/lib/Transforms/ObjCARC/ARCRuntimeEntryPoints.h +++ b/llvm/lib/Transforms/ObjCARC/ARCRuntimeEntryPoints.h @@ -46,6 +46,8 @@ enum class ARCRuntimeEntryPointKind { UnsafeClaimRV, RetainAutorelease, RetainAutoreleaseRV, + AutoreleasePoolPush, + AutoreleasePoolPop, }; /// Declarations for ObjC runtime functions and constants. These are initialized @@ -67,6 +69,8 @@ class ARCRuntimeEntryPoints { UnsafeClaimRV = nullptr; RetainAutorelease = nullptr; RetainAutoreleaseRV = nullptr; + AutoreleasePoolPush = nullptr; + AutoreleasePoolPop = nullptr; } Function *get(ARCRuntimeEntryPointKind kind) { @@ -101,6 +105,12 @@ class ARCRuntimeEntryPoints { case ARCRuntimeEntryPointKind::RetainAutoreleaseRV: return getIntrinsicEntryPoint(RetainAutoreleaseRV, Intrinsic::objc_retainAutoreleaseReturnValue); + case ARCRuntimeEntryPointKind::AutoreleasePoolPush: + return getIntrinsicEntryPoint(AutoreleasePoolPush, + Intrinsic::objc_autoreleasePoolPush); + case ARCRuntimeEntryPointKind::AutoreleasePoolPop: + return getIntrinsicEntryPoint(AutoreleasePoolPop, + Intrinsic::objc_autoreleasePoolPop); } llvm_unreachable("Switch should be a covered switch."); @@ -143,6 +153,12 @@ class ARCRuntimeEntryPoints { /// Declaration for objc_retainAutoreleaseReturnValue(). Function *RetainAutoreleaseRV = nullptr; + /// Declaration for objc_autoreleasePoolPush(). + Function *AutoreleasePoolPush = nullptr; + + /// Declaration for objc_autoreleasePoolPop(). + Function *AutoreleasePoolPop = nullptr; + Function *getIntrinsicEntryPoint(Function *&Decl, Intrinsic::ID IntID) { if (Decl) return Decl; diff --git a/llvm/lib/Transforms/ObjCARC/ObjCARCOpts.cpp b/llvm/lib/Transforms/ObjCARC/ObjCARCOpts.cpp index 5eb3f51d38945..deec643532c5d 100644 --- a/llvm/lib/Transforms/ObjCARC/ObjCARCOpts.cpp +++ b/llvm/lib/Transforms/ObjCARC/ObjCARCOpts.cpp @@ -39,6 +39,7 @@ #include "llvm/Analysis/ObjCARCAnalysisUtils.h" #include "llvm/Analysis/ObjCARCInstKind.h" #include "llvm/Analysis/ObjCARCUtil.h" +#include "llvm/Analysis/OptimizationRemarkEmitter.h" #include "llvm/IR/BasicBlock.h" #include "llvm/IR/CFG.h" #include "llvm/IR/Constant.h" @@ -132,11 +133,8 @@ static const Value *FindSingleUseIdentifiedObject(const Value *Arg) { // // The second retain and autorelease can be deleted. -// TODO: It should be possible to delete -// objc_autoreleasePoolPush and objc_autoreleasePoolPop -// pairs if nothing is actually autoreleased between them. Also, autorelease -// calls followed by objc_autoreleasePoolPop calls (perhaps in ObjC++ code -// after inlining) can be turned into plain release calls. +// TODO: Autorelease calls followed by objc_autoreleasePoolPop calls (perhaps in +// ObjC++ code after inlining) can be turned into plain release calls. // TODO: Critical-edge splitting. If the optimial insertion point is // a critical edge, the current algorithm has to fail, because it doesn't @@ -566,6 +564,8 @@ class ObjCARCOpt { void OptimizeReturns(Function &F); + void OptimizeAutoreleasePools(Function &F); + template <typename PredicateT> static void cloneOpBundlesIf(CallBase *CI, SmallVectorImpl<OperandBundleDef> &OpBundles, @@ -2473,6 +2473,11 @@ bool ObjCARCOpt::run(Function &F, AAResults &AA) { (1 << unsigned(ARCInstKind::AutoreleaseRV)))) OptimizeReturns(F); + // Optimizations for autorelease pools. + if (UsedInThisFunction & ((1 << unsigned(ARCInstKind::AutoreleasepoolPush)) | + (1 << unsigned(ARCInstKind::AutoreleasepoolPop)))) + OptimizeAutoreleasePools(F); + // Gather statistics after optimization. #ifndef NDEBUG if (AreStatisticsEnabled()) { @@ -2485,6 +2490,114 @@ bool ObjCARCOpt::run(Function &F, AAResults &AA) { return Changed; } +/// Optimize autorelease pools by eliminating empty push/pop pairs. +void ObjCARCOpt::OptimizeAutoreleasePools(Function &F) { + LLVM_DEBUG(dbgs() << "\n== ObjCARCOpt::OptimizeAutoreleasePools ==\n"); + + OptimizationRemarkEmitter ORE(&F); + + // Process each basic block independently. + // TODO: Can we optimize inter-block autorelease pool pairs? + // This would involve tracking autorelease pool state across blocks. + for (BasicBlock &BB : F) { + // Use a stack to track nested autorelease pools + SmallVector<std::pair<CallInst *, bool>, 4> + PoolStack; // {push_inst, has_autorelease_in_scope} + + for (Instruction &Inst : llvm::make_early_inc_range(BB)) { + ARCInstKind Class = GetBasicARCInstKind(&Inst); + + switch (Class) { + case ARCInstKind::AutoreleasepoolPush: { + // Start tracking a new autorelease pool scope + auto *Push = cast<CallInst>(&Inst); + PoolStack.push_back( + {Push, false}); // {push_inst, has_autorelease_in_scope} + LLVM_DEBUG(dbgs() << "Found autorelease pool push: " << *Push << "\n"); + break; + } + + case ARCInstKind::AutoreleasepoolPop: { + auto *Pop = cast<CallInst>(&Inst); + + if (PoolStack.empty()) + break; + + auto &TopPool = PoolStack.back(); + CallInst *PendingPush = TopPool.first; + bool HasAutoreleaseInScope = TopPool.second; + + // Pop the stack - remove this pool scope + PoolStack.pop_back(); + + // Bail if this pop doesn't match the pending push + if (Pop->getArgOperand(0)->stripPointerCasts() != PendingPush) + break; + + // Bail if there were autoreleases in this scope + if (HasAutoreleaseInScope) + break; + + // Optimize: eliminate this empty autorelease pool pair + ORE.emit([&]() { + return OptimizationRemark(DEBUG_TYPE, "AutoreleasePoolElimination", + PendingPush) + << "eliminated empty autorelease pool pair"; + }); + + // Replace all uses of push with poison before deletion + PendingPush->replaceAllUsesWith( + PoisonValue::get(PendingPush->getType())); + + PendingPush->eraseFromParent(); + Pop->eraseFromParent(); + + Changed = true; + ++NumNoops; + break; + } + case ARCInstKind::CallOrUser: + case ARCInstKind::Call: + case ARCInstKind::Autorelease: + case ARCInstKind::AutoreleaseRV: + case ARCInstKind::FusedRetainAutorelease: + case ARCInstKind::FusedRetainAutoreleaseRV: + case ARCInstKind::LoadWeak: { + // Track that we have autorelease calls in the current pool scope + if (!PoolStack.empty()) { + PoolStack.back().second = true; // Set has_autorelease_in_scope = true + LLVM_DEBUG( + dbgs() + << "Found autorelease or potiential autorelease in pool scope: " + << Inst << "\n"); + } + break; + } + + // Enumerate all remaining ARCInstKind cases explicitly + case ARCInstKind::Retain: + case ARCInstKind::RetainRV: + case ARCInstKind::UnsafeClaimRV: + case ARCInstKind::RetainBlock: + case ARCInstKind::Release: + case ARCInstKind::NoopCast: + case ARCInstKind::LoadWeakRetained: + case ARCInstKind::StoreWeak: + case ARCInstKind::InitWeak: + case ARCInstKind::MoveWeak: + case ARCInstKind::CopyWeak: + case ARCInstKind::DestroyWeak: + case ARCInstKind::StoreStrong: + case ARCInstKind::IntrinsicUser: + case ARCInstKind::User: + case ARCInstKind::None: + // These instruction kinds don't affect autorelease pool optimization + break; + } + } + } +} + /// @} /// diff --git a/llvm/test/Transforms/ObjCARC/test_autorelease_pool.ll b/llvm/test/Transforms/ObjCARC/test_autorelease_pool.ll new file mode 100644 index 0000000000000..ac27211a9e60c --- /dev/null +++ b/llvm/test/Transforms/ObjCARC/test_autorelease_pool.ll @@ -0,0 +1,118 @@ +; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 5 +; Test for autorelease pool optimizations +; RUN: opt -passes=objc-arc < %s -S | FileCheck %s + +declare ptr @llvm.objc.autoreleasePoolPush() +declare void @llvm.objc.autoreleasePoolPop(ptr) +declare ptr @llvm.objc.autorelease(ptr) +declare ptr @llvm.objc.retain(ptr) +declare ptr @create_object() +declare void @use_object(ptr) +declare ptr @object_with_thing() +declare void @opaque_callee() + +; Test 1: Empty autorelease pool should be eliminated +define void @test_empty_pool() { +; CHECK-LABEL: define void @test_empty_pool() { +; CHECK-NEXT: ret void +; + %pool = call ptr @llvm.objc.autoreleasePoolPush() + call void @llvm.objc.autoreleasePoolPop(ptr %pool) + ret void +} + +; Test 2: Pool with only release should be removed +define void @test_autorelease_to_release() { +; CHECK-LABEL: define void @test_autorelease_to_release() { +; CHECK-NEXT: [[OBJ:%.*]] = call ptr @create_object() +; CHECK-NEXT: call void @llvm.objc.release(ptr [[OBJ]]) #[[ATTR0:[0-9]+]], !clang.imprecise_release [[META0:![0-9]+]] +; CHECK-NEXT: ret void +; + %obj = call ptr @create_object() + %pool = call ptr @llvm.objc.autoreleasePoolPush() + call ptr @llvm.objc.autorelease(ptr %obj) + call void @llvm.objc.autoreleasePoolPop(ptr %pool) + ret void +} + +; Test 3: Pool with autoreleases should not be optimized +define void @test_multiple_autoreleases() { +; CHECK-LABEL: define void @test_multiple_autoreleases() { +; CHECK-NEXT: [[OBJ1:%.*]] = call ptr @create_object() +; CHECK-NEXT: [[OBJ2:%.*]] = call ptr @create_object() +; CHECK-NEXT: [[POOL:%.*]] = call ptr @llvm.objc.autoreleasePoolPush() #[[ATTR0]] +; CHECK-NEXT: call void @use_object(ptr [[OBJ1]]) +; CHECK-NEXT: [[TMP1:%.*]] = call ptr @llvm.objc.autorelease(ptr [[OBJ1]]) #[[ATTR0]] +; CHECK-NEXT: call void @use_object(ptr [[OBJ2]]) +; CHECK-NEXT: [[TMP2:%.*]] = call ptr @llvm.objc.autorelease(ptr [[OBJ2]]) #[[ATTR0]] +; CHECK-NEXT: call void @llvm.objc.autoreleasePoolPop(ptr [[POOL]]) #[[ATTR0]] +; CHECK-NEXT: ret void +; + %obj1 = call ptr @create_object() + %obj2 = call ptr @create_object() + %pool = call ptr @llvm.objc.autoreleasePoolPush() + call void @use_object(ptr %obj1) + call ptr @llvm.objc.autorelease(ptr %obj1) + call void @use_object(ptr %obj2) + call ptr @llvm.objc.autorelease(ptr %obj2) + call void @llvm.objc.autoreleasePoolPop(ptr %pool) + ret void +} + +; Test 4: Pool with calls should not be optimized +define void @test_calls() { +; CHECK-LABEL: define void @test_calls() { +; CHECK-NEXT: [[POOL:%.*]] = call ptr @llvm.objc.autoreleasePoolPush() #[[ATTR0]] +; CHECK-NEXT: [[OBJ1:%.*]] = call ptr @object_with_thing() +; CHECK-NEXT: call void @use_object(ptr [[OBJ1]]) +; CHECK-NEXT: call void @llvm.objc.autoreleasePoolPop(ptr [[POOL]]) #[[ATTR0]] +; CHECK-NEXT: ret void +; + %pool = call ptr @llvm.objc.autoreleasePoolPush() + %obj1 = call ptr @object_with_thing() + call void @use_object(ptr %obj1) + call void @llvm.objc.autoreleasePoolPop(ptr %pool) + ret void +} + +; Test 5: Pool with opaque call should not be optimized +define void @test_opaque_call() { +; CHECK-LABEL: define void @test_opaque_call() { +; CHECK-NEXT: [[POOL:%.*]] = call ptr @llvm.objc.autoreleasePoolPush() #[[ATTR0]] +; CHECK-NEXT: call void @opaque_callee() +; CHECK-NEXT: call void @llvm.objc.autoreleasePoolPop(ptr [[POOL]]) #[[ATTR0]] +; CHECK-NEXT: ret void +; + %pool = call ptr @llvm.objc.autoreleasePoolPush() + call void @opaque_callee() + call void @llvm.objc.autoreleasePoolPop(ptr %pool) + ret void +} + +; Test 6: Nested empty pools should be eliminated +define void @test_nested_empty_pools() { +; CHECK-LABEL: define void @test_nested_empty_pools() { +; CHECK-NEXT: ret void +; + %pool1 = call ptr @llvm.objc.autoreleasePoolPush() + %pool2 = call ptr @llvm.objc.autoreleasePoolPush() + call void @llvm.objc.autoreleasePoolPop(ptr %pool2) + call void @llvm.objc.autoreleasePoolPop(ptr %pool1) + ret void +} + +; Test 7: Empty pool with cast should be eliminated +define void @test_empty_pool_with_cast() { +; CHECK-LABEL: define void @test_empty_pool_with_cast() { +; CHECK-NEXT: [[CAST:%.*]] = bitcast ptr poison to ptr +; CHECK-NEXT: ret void +; + %pool = call ptr @llvm.objc.autoreleasePoolPush() + %cast = bitcast ptr %pool to ptr + call void @llvm.objc.autoreleasePoolPop(ptr %cast) + ret void +} + +;. +; CHECK: [[META0]] = !{} +;. >From f06621062a00a2aba4a86fd214a9c8c57a8b4d30 Mon Sep 17 00:00:00 2001 From: Rose <gfunni...@gmail.com> Date: Thu, 19 Jun 2025 16:10:14 -0400 Subject: [PATCH 2/2] Delete ObjCARCAPElim, as it is entirely superseded by ObjCARCOpts The optimization can be completely superseded now --- clang/lib/CodeGen/BackendUtil.cpp | 6 - llvm/include/llvm/Transforms/ObjCARC.h | 4 - llvm/lib/Passes/PassRegistry.def | 1 - llvm/lib/Transforms/ObjCARC/CMakeLists.txt | 1 - llvm/lib/Transforms/ObjCARC/ObjCARCAPElim.cpp | 156 -------------- llvm/lib/Transforms/ObjCARC/ObjCARCOpts.cpp | 119 +++++++++++ llvm/test/Transforms/ObjCARC/apelim.ll | 2 +- llvm/test/Transforms/ObjCARC/comdat-ipo.ll | 2 +- .../ObjCARC/test_autorelease_pool.ll | 201 ++++++++++++++++++ 9 files changed, 322 insertions(+), 170 deletions(-) delete mode 100644 llvm/lib/Transforms/ObjCARC/ObjCARCAPElim.cpp diff --git a/clang/lib/CodeGen/BackendUtil.cpp b/clang/lib/CodeGen/BackendUtil.cpp index 7e0a3cf5591ce..ab2707e0d1c05 100644 --- a/clang/lib/CodeGen/BackendUtil.cpp +++ b/clang/lib/CodeGen/BackendUtil.cpp @@ -1022,12 +1022,6 @@ void EmitAssemblyHelper::RunOptimizationPipeline( MPM.addPass( createModuleToFunctionPassAdaptor(ObjCARCExpandPass())); }); - PB.registerPipelineEarlySimplificationEPCallback( - [](ModulePassManager &MPM, OptimizationLevel Level, - ThinOrFullLTOPhase) { - if (Level != OptimizationLevel::O0) - MPM.addPass(ObjCARCAPElimPass()); - }); PB.registerScalarOptimizerLateEPCallback( [](FunctionPassManager &FPM, OptimizationLevel Level) { if (Level != OptimizationLevel::O0) diff --git a/llvm/include/llvm/Transforms/ObjCARC.h b/llvm/include/llvm/Transforms/ObjCARC.h index c927513469a35..c4b4c4f0b09c6 100644 --- a/llvm/include/llvm/Transforms/ObjCARC.h +++ b/llvm/include/llvm/Transforms/ObjCARC.h @@ -35,10 +35,6 @@ struct ObjCARCContractPass : public PassInfoMixin<ObjCARCContractPass> { LLVM_ABI PreservedAnalyses run(Function &F, FunctionAnalysisManager &AM); }; -struct ObjCARCAPElimPass : public PassInfoMixin<ObjCARCAPElimPass> { - LLVM_ABI PreservedAnalyses run(Module &M, ModuleAnalysisManager &AM); -}; - struct ObjCARCExpandPass : public PassInfoMixin<ObjCARCExpandPass> { LLVM_ABI PreservedAnalyses run(Function &M, FunctionAnalysisManager &AM); }; diff --git a/llvm/lib/Passes/PassRegistry.def b/llvm/lib/Passes/PassRegistry.def index ec14c6a9211d9..6d184d71b92cb 100644 --- a/llvm/lib/Passes/PassRegistry.def +++ b/llvm/lib/Passes/PassRegistry.def @@ -113,7 +113,6 @@ MODULE_PASS("module-inline", ModuleInlinerPass()) MODULE_PASS("name-anon-globals", NameAnonGlobalPass()) MODULE_PASS("no-op-module", NoOpModulePass()) MODULE_PASS("nsan", NumericalStabilitySanitizerPass()) -MODULE_PASS("objc-arc-apelim", ObjCARCAPElimPass()) MODULE_PASS("openmp-opt", OpenMPOptPass()) MODULE_PASS("openmp-opt-postlink", OpenMPOptPass(ThinOrFullLTOPhase::FullLTOPostLink)) diff --git a/llvm/lib/Transforms/ObjCARC/CMakeLists.txt b/llvm/lib/Transforms/ObjCARC/CMakeLists.txt index 80867dbc270d7..4274667a2c2b7 100644 --- a/llvm/lib/Transforms/ObjCARC/CMakeLists.txt +++ b/llvm/lib/Transforms/ObjCARC/CMakeLists.txt @@ -2,7 +2,6 @@ add_llvm_component_library(LLVMObjCARCOpts ObjCARC.cpp ObjCARCOpts.cpp ObjCARCExpand.cpp - ObjCARCAPElim.cpp ObjCARCContract.cpp DependencyAnalysis.cpp ProvenanceAnalysis.cpp diff --git a/llvm/lib/Transforms/ObjCARC/ObjCARCAPElim.cpp b/llvm/lib/Transforms/ObjCARC/ObjCARCAPElim.cpp deleted file mode 100644 index dceb2ebb1863e..0000000000000 --- a/llvm/lib/Transforms/ObjCARC/ObjCARCAPElim.cpp +++ /dev/null @@ -1,156 +0,0 @@ -//===- ObjCARCAPElim.cpp - ObjC ARC Optimization --------------------------===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// -/// \file -/// -/// This file defines ObjC ARC optimizations. ARC stands for Automatic -/// Reference Counting and is a system for managing reference counts for objects -/// in Objective C. -/// -/// This specific file implements optimizations which remove extraneous -/// autorelease pools. -/// -/// WARNING: This file knows about certain library functions. It recognizes them -/// by name, and hardwires knowledge of their semantics. -/// -/// WARNING: This file knows about how certain Objective-C library functions are -/// used. Naive LLVM IR transformations which would otherwise be -/// behavior-preserving may break these assumptions. -/// -//===----------------------------------------------------------------------===// - -#include "llvm/ADT/STLExtras.h" -#include "llvm/Analysis/ObjCARCAnalysisUtils.h" -#include "llvm/Analysis/ObjCARCInstKind.h" -#include "llvm/IR/Constants.h" -#include "llvm/IR/InstrTypes.h" -#include "llvm/IR/PassManager.h" -#include "llvm/Support/Debug.h" -#include "llvm/Support/raw_ostream.h" -#include "llvm/Transforms/ObjCARC.h" - -using namespace llvm; -using namespace llvm::objcarc; - -#define DEBUG_TYPE "objc-arc-ap-elim" - -namespace { - -/// Interprocedurally determine if calls made by the given call site can -/// possibly produce autoreleases. -bool MayAutorelease(const CallBase &CB, unsigned Depth = 0) { - if (const Function *Callee = CB.getCalledFunction()) { - if (!Callee->hasExactDefinition()) - return true; - for (const BasicBlock &BB : *Callee) { - for (const Instruction &I : BB) - if (const CallBase *JCB = dyn_cast<CallBase>(&I)) - // This recursion depth limit is arbitrary. It's just great - // enough to cover known interesting testcases. - if (Depth < 3 && !JCB->onlyReadsMemory() && - MayAutorelease(*JCB, Depth + 1)) - return true; - } - return false; - } - - return true; -} - -bool OptimizeBB(BasicBlock *BB) { - bool Changed = false; - - Instruction *Push = nullptr; - for (Instruction &Inst : llvm::make_early_inc_range(*BB)) { - switch (GetBasicARCInstKind(&Inst)) { - case ARCInstKind::AutoreleasepoolPush: - Push = &Inst; - break; - case ARCInstKind::AutoreleasepoolPop: - // If this pop matches a push and nothing in between can autorelease, - // zap the pair. - if (Push && cast<CallInst>(&Inst)->getArgOperand(0) == Push) { - Changed = true; - LLVM_DEBUG(dbgs() << "ObjCARCAPElim::OptimizeBB: Zapping push pop " - "autorelease pair:\n" - " Pop: " - << Inst << "\n" - << " Push: " << *Push - << "\n"); - Inst.eraseFromParent(); - Push->eraseFromParent(); - } - Push = nullptr; - break; - case ARCInstKind::CallOrUser: - if (MayAutorelease(cast<CallBase>(Inst))) - Push = nullptr; - break; - default: - break; - } - } - - return Changed; -} - -bool runImpl(Module &M) { - if (!EnableARCOpts) - return false; - - // If nothing in the Module uses ARC, don't do anything. - if (!ModuleHasARC(M)) - return false; - // Find the llvm.global_ctors variable, as the first step in - // identifying the global constructors. In theory, unnecessary autorelease - // pools could occur anywhere, but in practice it's pretty rare. Global - // ctors are a place where autorelease pools get inserted automatically, - // so it's pretty common for them to be unnecessary, and it's pretty - // profitable to eliminate them. - GlobalVariable *GV = M.getGlobalVariable("llvm.global_ctors"); - if (!GV) - return false; - - assert(GV->hasDefinitiveInitializer() && - "llvm.global_ctors is uncooperative!"); - - bool Changed = false; - - // Dig the constructor functions out of GV's initializer. - ConstantArray *Init = cast<ConstantArray>(GV->getInitializer()); - for (User::op_iterator OI = Init->op_begin(), OE = Init->op_end(); - OI != OE; ++OI) { - Value *Op = *OI; - // llvm.global_ctors is an array of three-field structs where the second - // members are constructor functions. - Function *F = dyn_cast<Function>(cast<ConstantStruct>(Op)->getOperand(1)); - // If the user used a constructor function with the wrong signature and - // it got bitcasted or whatever, look the other way. - if (!F) - continue; - // Only look at function definitions. - if (F->isDeclaration()) - continue; - // Only look at functions with one basic block. - if (std::next(F->begin()) != F->end()) - continue; - // Ok, a single-block constructor function definition. Try to optimize it. - Changed |= OptimizeBB(&F->front()); - } - - return Changed; -} - -} // namespace - -PreservedAnalyses ObjCARCAPElimPass::run(Module &M, ModuleAnalysisManager &AM) { - if (!runImpl(M)) - return PreservedAnalyses::all(); - PreservedAnalyses PA; - PA.preserveSet<CFGAnalyses>(); - return PA; -} diff --git a/llvm/lib/Transforms/ObjCARC/ObjCARCOpts.cpp b/llvm/lib/Transforms/ObjCARC/ObjCARCOpts.cpp index deec643532c5d..93cb469da3b46 100644 --- a/llvm/lib/Transforms/ObjCARC/ObjCARCOpts.cpp +++ b/llvm/lib/Transforms/ObjCARC/ObjCARCOpts.cpp @@ -2490,6 +2490,122 @@ bool ObjCARCOpt::run(Function &F, AAResults &AA) { return Changed; } +/// Interprocedurally determine if calls made by the given call site can +/// possibly produce autoreleases. +bool MayAutorelease(const CallBase &CB, unsigned Depth = 0) { + if (CB.onlyReadsMemory()) + return false; + + // This recursion depth limit is arbitrary. It's just great + // enough to cover known interesting testcases. + if (Depth > 5) + return true; + + if (const Function *Callee = CB.getCalledFunction()) { + if (!Callee->hasExactDefinition()) + return true; + for (const BasicBlock &BB : *Callee) { + // If we come across an autoreleasepool push and a pop in the same block, + // we can ignore the instructions between them. + // TODO: Can we check inter-block autorelease pool pairs? + // This would involve tracking autorelease pool state across blocks. + + // First pass: find all push/pop pairs and their positions in this basic + // block + SmallVector<std::pair<unsigned, unsigned>, 4> + ShadowedRegions; // (push_pos, pop_pos) + SmallVector<std::pair<unsigned, const Instruction*>, 4> PushStack; // (index, push_inst) + + unsigned InstIndex = 0; + for (const Instruction &I : BB) { + if (GetBasicARCInstKind(&I) == ARCInstKind::AutoreleasepoolPush) { + PushStack.push_back({InstIndex, &I}); + } else if (GetBasicARCInstKind(&I) == ARCInstKind::AutoreleasepoolPop) { + if (!PushStack.empty()) { + auto [PushIndex, PushInst] = PushStack.back(); + // Verify this pop actually pops the corresponding push + const auto *PopCall = cast<CallInst>(&I); + if (PopCall->getArgOperand(0)->stripPointerCasts() == PushInst) { + // Valid push/pop pair - add to shadowed regions + ShadowedRegions.push_back({PushIndex, InstIndex}); + PushStack.pop_back(); + } + // If pop doesn't match the most recent push, it might be for an outer pool + // in a different basic block, so we leave the stack unchanged + } + } + InstIndex++; + } + + // Second pass: check for autoreleases, ignoring shadowed regions + InstIndex = 0; + for (const Instruction &I : BB) { + // Check if this instruction is in a shadowed region (between inner + // push/pop pairs) + bool InShadowedRegion = false; + for (auto [PushIndex, PopIndex] : ShadowedRegions) { + if (InstIndex > PushIndex && InstIndex < PopIndex) { + InShadowedRegion = true; + break; + } + } + + if (InShadowedRegion) { + InstIndex++; + continue; // Skip instructions in shadowed regions - they don't affect + // outer pools + } + + ARCInstKind InstKind = GetBasicARCInstKind(&I); + switch (InstKind) { + case ARCInstKind::Autorelease: + case ARCInstKind::AutoreleaseRV: + case ARCInstKind::FusedRetainAutorelease: + case ARCInstKind::FusedRetainAutoreleaseRV: + case ARCInstKind::LoadWeak: + // These may produce autoreleases + return true; + + case ARCInstKind::Retain: + case ARCInstKind::RetainRV: + case ARCInstKind::UnsafeClaimRV: + case ARCInstKind::RetainBlock: + case ARCInstKind::Release: + case ARCInstKind::NoopCast: + case ARCInstKind::LoadWeakRetained: + case ARCInstKind::StoreWeak: + case ARCInstKind::InitWeak: + case ARCInstKind::MoveWeak: + case ARCInstKind::CopyWeak: + case ARCInstKind::DestroyWeak: + case ARCInstKind::StoreStrong: + case ARCInstKind::AutoreleasepoolPush: + case ARCInstKind::AutoreleasepoolPop: + // These ObjC runtime functions don't produce autoreleases + break; + + case ARCInstKind::CallOrUser: + case ARCInstKind::Call: + // For non-ObjC function calls, recursively analyze + if (MayAutorelease(cast<CallBase>(I), Depth + 1)) + return true; + break; + + case ARCInstKind::IntrinsicUser: + case ARCInstKind::User: + case ARCInstKind::None: + // These are not relevant for autorelease analysis + break; + } + InstIndex++; + } + } + return false; + } + + return true; +} + /// Optimize autorelease pools by eliminating empty push/pop pairs. void ObjCARCOpt::OptimizeAutoreleasePools(Function &F) { LLVM_DEBUG(dbgs() << "\n== ObjCARCOpt::OptimizeAutoreleasePools ==\n"); @@ -2558,6 +2674,9 @@ void ObjCARCOpt::OptimizeAutoreleasePools(Function &F) { } case ARCInstKind::CallOrUser: case ARCInstKind::Call: + if (!MayAutorelease(cast<CallBase>(Inst))) + break; + [[fallthrough]]; case ARCInstKind::Autorelease: case ARCInstKind::AutoreleaseRV: case ARCInstKind::FusedRetainAutorelease: diff --git a/llvm/test/Transforms/ObjCARC/apelim.ll b/llvm/test/Transforms/ObjCARC/apelim.ll index 2ac5d15d0df85..01179f3dec048 100644 --- a/llvm/test/Transforms/ObjCARC/apelim.ll +++ b/llvm/test/Transforms/ObjCARC/apelim.ll @@ -1,4 +1,4 @@ -; RUN: opt -S -passes=objc-arc-apelim < %s | FileCheck %s +; RUN: opt -S -passes=objc-arc < %s | FileCheck %s ; rdar://10227311 @llvm.global_ctors = appending global [2 x { i32, ptr, ptr }] [{ i32, ptr, ptr } { i32 65535, ptr @_GLOBAL__I_x, ptr null }, { i32, ptr, ptr } { i32 65535, ptr @_GLOBAL__I_y, ptr null }] diff --git a/llvm/test/Transforms/ObjCARC/comdat-ipo.ll b/llvm/test/Transforms/ObjCARC/comdat-ipo.ll index 3f91d3bea9f14..d43804c20d936 100644 --- a/llvm/test/Transforms/ObjCARC/comdat-ipo.ll +++ b/llvm/test/Transforms/ObjCARC/comdat-ipo.ll @@ -1,4 +1,4 @@ -; RUN: opt -S -passes=objc-arc-apelim < %s | FileCheck %s +; RUN: opt -S -passes=objc-arc < %s | FileCheck %s ; See PR26774 diff --git a/llvm/test/Transforms/ObjCARC/test_autorelease_pool.ll b/llvm/test/Transforms/ObjCARC/test_autorelease_pool.ll index ac27211a9e60c..1238170e0e1fd 100644 --- a/llvm/test/Transforms/ObjCARC/test_autorelease_pool.ll +++ b/llvm/test/Transforms/ObjCARC/test_autorelease_pool.ll @@ -113,6 +113,207 @@ define void @test_empty_pool_with_cast() { ret void } +; Test 8: Autorelease shadowing - autorelease in inner pool doesn't prevent outer optimization +define void @test_autorelease_shadowing_basic() { +; CHECK-LABEL: define void @test_autorelease_shadowing_basic() { +; CHECK-NEXT: [[OBJ:%.*]] = call ptr @create_object() +; CHECK-NEXT: call void @llvm.objc.release(ptr [[OBJ]]) #[[ATTR0]], !clang.imprecise_release [[META0]] +; CHECK-NEXT: ret void +; + %obj = call ptr @create_object() + %outer_pool = call ptr @llvm.objc.autoreleasePoolPush() + + ; Inner pool with autorelease - this should be shadowed + %inner_pool = call ptr @llvm.objc.autoreleasePoolPush() + call ptr @llvm.objc.autorelease(ptr %obj) + call void @llvm.objc.autoreleasePoolPop(ptr %inner_pool) + + call void @llvm.objc.autoreleasePoolPop(ptr %outer_pool) + ret void +} + +; Test 9: Multiple nested levels with shadowing +define void @test_multiple_nested_shadowing() { +; CHECK-LABEL: define void @test_multiple_nested_shadowing() { +; CHECK-NEXT: [[OBJ1:%.*]] = call ptr @create_object() +; CHECK-NEXT: [[OBJ2:%.*]] = call ptr @create_object() +; CHECK-NEXT: call void @llvm.objc.release(ptr [[OBJ1]]) #[[ATTR0]], !clang.imprecise_release [[META0]] +; CHECK-NEXT: call void @llvm.objc.release(ptr [[OBJ2]]) #[[ATTR0]], !clang.imprecise_release [[META0]] +; CHECK-NEXT: ret void +; + %obj1 = call ptr @create_object() + %obj2 = call ptr @create_object() + %outer_pool = call ptr @llvm.objc.autoreleasePoolPush() + + ; First inner pool + %inner1_pool = call ptr @llvm.objc.autoreleasePoolPush() + call ptr @llvm.objc.autorelease(ptr %obj1) + call void @llvm.objc.autoreleasePoolPop(ptr %inner1_pool) + + ; Second inner pool with nested level + %inner2_pool = call ptr @llvm.objc.autoreleasePoolPush() + %inner3_pool = call ptr @llvm.objc.autoreleasePoolPush() + call ptr @llvm.objc.autorelease(ptr %obj2) + call void @llvm.objc.autoreleasePoolPop(ptr %inner3_pool) + call void @llvm.objc.autoreleasePoolPop(ptr %inner2_pool) + + call void @llvm.objc.autoreleasePoolPop(ptr %outer_pool) + ret void +} + +; Test 10: Autorelease outside inner pool prevents optimization +define void @test_autorelease_outside_inner_pool() { +; CHECK-LABEL: define void @test_autorelease_outside_inner_pool() { +; CHECK-NEXT: [[OBJ1:%.*]] = call ptr @create_object() +; CHECK-NEXT: [[OBJ2:%.*]] = call ptr @create_object() +; CHECK-NEXT: call void @llvm.objc.release(ptr [[OBJ1]]) #[[ATTR0]], !clang.imprecise_release [[META0]] +; CHECK-NEXT: call void @llvm.objc.release(ptr [[OBJ2]]) #[[ATTR0]], !clang.imprecise_release [[META0]] +; CHECK-NEXT: ret void +; + %obj1 = call ptr @create_object() + %obj2 = call ptr @create_object() + %outer_pool = call ptr @llvm.objc.autoreleasePoolPush() + + ; This autorelease is NOT in an inner pool, so outer pool can't be optimized + call ptr @llvm.objc.autorelease(ptr %obj1) + + ; Inner pool with autorelease (shadowed) + %inner_pool = call ptr @llvm.objc.autoreleasePoolPush() + call ptr @llvm.objc.autorelease(ptr %obj2) + call void @llvm.objc.autoreleasePoolPop(ptr %inner_pool) + + call void @llvm.objc.autoreleasePoolPop(ptr %outer_pool) + ret void +} + +; Test 11: Known ObjC functions don't prevent optimization +define void @test_known_objc_functions() { +; CHECK-LABEL: define void @test_known_objc_functions() { +; CHECK-NEXT: [[OBJ:%.*]] = call ptr @create_object() +; CHECK-NEXT: ret void +; + %obj = call ptr @create_object() + %pool = call ptr @llvm.objc.autoreleasePoolPush() + + ; These are all known ObjC runtime functions that don't produce autoreleases + %retained = call ptr @llvm.objc.retain(ptr %obj) + call void @llvm.objc.release(ptr %obj) + + call void @llvm.objc.autoreleasePoolPop(ptr %pool) + ret void +} + +; Test 12: Complex shadowing with mixed autoreleases +define void @test_complex_shadowing() { +; CHECK-LABEL: define void @test_complex_shadowing() { +; CHECK-NEXT: [[OBJ1:%.*]] = call ptr @create_object() +; CHECK-NEXT: [[OBJ2:%.*]] = call ptr @create_object() +; CHECK-NEXT: [[OBJ3:%.*]] = call ptr @create_object() +; CHECK-NEXT: call void @llvm.objc.release(ptr [[OBJ1]]) #[[ATTR0]], !clang.imprecise_release [[META0]] +; CHECK-NEXT: call void @llvm.objc.release(ptr [[OBJ2]]) #[[ATTR0]], !clang.imprecise_release [[META0]] +; CHECK-NEXT: [[INNER2_POOL:%.*]] = call ptr @llvm.objc.autoreleasePoolPush() #[[ATTR0]] +; CHECK-NEXT: [[TMP1:%.*]] = call ptr @llvm.objc.autorelease(ptr [[OBJ3]]) #[[ATTR0]] +; CHECK-NEXT: call void @llvm.objc.autoreleasePoolPop(ptr [[INNER2_POOL]]) #[[ATTR0]] +; CHECK-NEXT: ret void +; + %obj1 = call ptr @create_object() + %obj2 = call ptr @create_object() + %obj3 = call ptr @create_object() + %outer_pool = call ptr @llvm.objc.autoreleasePoolPush() + + ; This autorelease is outside inner pools - prevents optimization + call ptr @llvm.objc.autorelease(ptr %obj1) + + ; Inner pool 1 with shadowed autorelease + %inner1_pool = call ptr @llvm.objc.autoreleasePoolPush() + call ptr @llvm.objc.autorelease(ptr %obj2) + call void @llvm.objc.autoreleasePoolPop(ptr %inner1_pool) + + ; Some safe ObjC operations + %retained = call ptr @llvm.objc.retain(ptr %obj3) + call void @llvm.objc.release(ptr %retained) + + ; Inner pool 2 with shadowed autorelease + %inner2_pool = call ptr @llvm.objc.autoreleasePoolPush() + call ptr @llvm.objc.autorelease(ptr %obj3) + call void @llvm.objc.autoreleasePoolPop(ptr %inner2_pool) + + call void @llvm.objc.autoreleasePoolPop(ptr %outer_pool) + ret void +} + +; Test 13: Non-ObjC function that may autorelease prevents optimization +define void @test_non_objc_may_autorelease() { +; CHECK-LABEL: define void @test_non_objc_may_autorelease() { +; CHECK-NEXT: [[POOL:%.*]] = call ptr @llvm.objc.autoreleasePoolPush() #[[ATTR0]] +; CHECK-NEXT: [[TMP1:%.*]] = call ptr @function_that_might_autorelease() +; CHECK-NEXT: call void @llvm.objc.autoreleasePoolPop(ptr [[POOL]]) #[[ATTR0]] +; CHECK-NEXT: ret void +; + %pool = call ptr @llvm.objc.autoreleasePoolPush() + call ptr @function_that_might_autorelease() + call void @llvm.objc.autoreleasePoolPop(ptr %pool) + ret void +} + +; Test 14: Non-ObjC function that doesn't autorelease allows optimization +define void @test_non_objc_no_autorelease() { +; CHECK-LABEL: define void @test_non_objc_no_autorelease() { +; CHECK-NEXT: call void @safe_function() +; CHECK-NEXT: ret void +; + %pool = call ptr @llvm.objc.autoreleasePoolPush() + call void @safe_function() + call void @llvm.objc.autoreleasePoolPop(ptr %pool) + ret void +} + +; Test 15: Incomplete push/pop pairs across blocks - only inner pairs count +define void @test_incomplete_pairs_inner_shadowing() { +; CHECK-LABEL: define void @test_incomplete_pairs_inner_shadowing() { +; CHECK-NEXT: [[OBJ:%.*]] = call ptr @create_object() +; CHECK-NEXT: [[OUTER_POOL:%.*]] = call ptr @llvm.objc.autoreleasePoolPush() #[[ATTR0]] +; CHECK-NEXT: call void @llvm.objc.release(ptr [[OBJ]]) #[[ATTR0]], !clang.imprecise_release [[META0]] +; CHECK-NEXT: ret void +; + %obj = call ptr @create_object() + %outer_pool = call ptr @llvm.objc.autoreleasePoolPush() + + ; Inner complete pair - autorelease should be shadowed by this + %inner_pool = call ptr @llvm.objc.autoreleasePoolPush() + call ptr @llvm.objc.autorelease(ptr %obj) ; This SHOULD be shadowed by inner pair + call void @llvm.objc.autoreleasePoolPop(ptr %inner_pool) ; Completes the inner pair + + ; Note: %outer_pool pop is in a different block (common pattern) + ; But the autorelease was shadowed by the complete inner pair + ret void +} + +; Helper functions for testing interprocedural analysis + +; Safe function that doesn't call autorelease +define void @safe_function() { + ; Just some computation, no autoreleases +; CHECK-LABEL: define void @safe_function() { +; CHECK-NEXT: [[X:%.*]] = add i32 1, 2 +; CHECK-NEXT: ret void +; + %x = add i32 1, 2 + ret void +} + +; Function that may produce autoreleases (simulated by calling autorelease) +define ptr @function_that_might_autorelease() { +; CHECK-LABEL: define ptr @function_that_might_autorelease() { +; CHECK-NEXT: [[OBJ:%.*]] = call ptr @create_object() +; CHECK-NEXT: [[AUTORELEASED:%.*]] = call ptr @llvm.objc.autorelease(ptr [[OBJ]]) #[[ATTR0]] +; CHECK-NEXT: ret ptr [[AUTORELEASED]] +; + %obj = call ptr @create_object() + %autoreleased = call ptr @llvm.objc.autorelease(ptr %obj) + ret ptr %autoreleased +} + ;. ; CHECK: [[META0]] = !{} ;. _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits