Marina Taylor 1914184e5c
Reland "[ObjCARC][Contract] Optimize bundled RetainRV to ClaimRV" (#139889)
This teaches ObjCARCContract to transform attachedcall bundles
referencing objc_retainAutoreleasedReturnValue to instead reference
objc_claimAutoreleasedReturnValue.

The only distinction between the two is that the latter is required to
be guaranteed to immediately follow the call it's attached to, and, by
construction, the bundles always achieve that by:
- not being separable from the call through IR and the backend
- not getting the marker emitted when claimARV is the attachedcall.

This is enabled only for arm64, arm64e, and arm64_32 on macOS13/iOS16
and related operating systems.

Co-authored-by: Ahmed Bougacha <ahmed@bougacha.org>
2025-05-14 14:21:52 +01:00

140 lines
5.2 KiB
C++

//===-- ObjCARC.cpp -------------------------------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//
//
// This file implements common infrastructure for libLLVMObjCARCOpts.a, which
// implements several scalar transformations over the LLVM intermediate
// representation, including the C bindings for that library.
//
//===----------------------------------------------------------------------===//
#include "ObjCARC.h"
#include "llvm/Analysis/ObjCARCUtil.h"
#include "llvm/IR/IRBuilder.h"
#include "llvm/IR/Instructions.h"
#include "llvm/Transforms/Utils/BasicBlockUtils.h"
using namespace llvm;
using namespace llvm::objcarc;
CallInst *objcarc::createCallInstWithColors(
FunctionCallee Func, ArrayRef<Value *> Args, const Twine &NameStr,
BasicBlock::iterator InsertBefore,
const DenseMap<BasicBlock *, ColorVector> &BlockColors) {
FunctionType *FTy = Func.getFunctionType();
Value *Callee = Func.getCallee();
SmallVector<OperandBundleDef, 1> OpBundles;
if (!BlockColors.empty()) {
const ColorVector &CV = BlockColors.find(InsertBefore->getParent())->second;
assert(CV.size() == 1 && "non-unique color for block!");
BasicBlock::iterator EHPad = CV.front()->getFirstNonPHIIt();
if (EHPad->isEHPad())
OpBundles.emplace_back("funclet", &*EHPad);
}
return CallInst::Create(FTy, Callee, Args, OpBundles, NameStr, InsertBefore);
}
std::pair<bool, bool>
BundledRetainClaimRVs::insertAfterInvokes(Function &F, DominatorTree *DT) {
bool Changed = false, CFGChanged = false;
for (BasicBlock &BB : F) {
auto *I = dyn_cast<InvokeInst>(BB.getTerminator());
if (!I)
continue;
if (!objcarc::hasAttachedCallOpBundle(I))
continue;
BasicBlock *DestBB = I->getNormalDest();
if (!DestBB->getSinglePredecessor()) {
assert(I->getSuccessor(0) == DestBB &&
"the normal dest is expected to be the first successor");
DestBB = SplitCriticalEdge(I, 0, CriticalEdgeSplittingOptions(DT));
CFGChanged = true;
}
// We don't have to call insertRVCallWithColors since DestBB is the normal
// destination of the invoke.
insertRVCall(DestBB->getFirstInsertionPt(), I);
Changed = true;
}
return std::make_pair(Changed, CFGChanged);
}
CallInst *BundledRetainClaimRVs::insertRVCall(BasicBlock::iterator InsertPt,
CallBase *AnnotatedCall) {
DenseMap<BasicBlock *, ColorVector> BlockColors;
return insertRVCallWithColors(InsertPt, AnnotatedCall, BlockColors);
}
CallInst *BundledRetainClaimRVs::insertRVCallWithColors(
BasicBlock::iterator InsertPt, CallBase *AnnotatedCall,
const DenseMap<BasicBlock *, ColorVector> &BlockColors) {
IRBuilder<> Builder(InsertPt->getParent(), InsertPt);
Function *Func = *objcarc::getAttachedARCFunction(AnnotatedCall);
assert(Func && "operand isn't a Function");
Type *ParamTy = Func->getArg(0)->getType();
Value *CallArg = Builder.CreateBitCast(AnnotatedCall, ParamTy);
auto *Call =
createCallInstWithColors(Func, CallArg, "", InsertPt, BlockColors);
RVCalls[Call] = AnnotatedCall;
return Call;
}
BundledRetainClaimRVs::~BundledRetainClaimRVs() {
for (auto P : RVCalls) {
if (ContractPass) {
CallBase *CB = P.second;
// At this point, we know that the annotated calls can't be tail calls
// as they are followed by marker instructions and retainRV/claimRV
// calls. Mark them as notail so that the backend knows these calls
// can't be tail calls.
if (auto *CI = dyn_cast<CallInst>(CB))
CI->setTailCallKind(CallInst::TCK_NoTail);
// We can also do one final optimization: modify the bundle in the
// annotated call, to change the bundle operand from
// objc_retainAutoreleasedReturnValue
// to:
// objc_claimAutoreleasedReturnValue
// allowing the marker to be omitted from the bundle expansion later.
//
// Note that, confusingly, ClaimRV is semantically equivalent to RetainRV,
// and only differs in that it doesn't require the marker.
// The bundle provides the guarantee that we're emitting the ClaimRV call
// adjacent to the original call, and providing that guarantee is the
// only difference between ClaimRV and RetainRV.
//
// UnsafeClaimRV has a different RC contract entirely.
// Find the clang.arc.attachedcall bundle, and rewrite its operand.
if (UseClaimRV) {
for (auto OBI : CB->bundle_op_infos()) {
auto OBU = CB->operandBundleFromBundleOpInfo(OBI);
if (OBU.getTagID() == LLVMContext::OB_clang_arc_attachedcall &&
OBU.Inputs[0] == EP.get(ARCRuntimeEntryPointKind::RetainRV)) {
CB->setOperand(OBI.Begin,
EP.get(ARCRuntimeEntryPointKind::ClaimRV));
break;
}
}
}
}
// Erase the RV call we emitted earlier: it's already in the bundle.
EraseInstruction(P.first);
}
RVCalls.clear();
}