Skip to content

Commit 1914184

Browse files
Reland "[ObjCARC][Contract] Optimize bundled RetainRV to ClaimRV" (llvm#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>
1 parent a31d7d1 commit 1914184

File tree

7 files changed

+125
-3
lines changed

7 files changed

+125
-3
lines changed

llvm/lib/Transforms/ObjCARC/ARCRuntimeEntryPoints.h

+8
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ enum class ARCRuntimeEntryPointKind {
4242
Autorelease,
4343
StoreStrong,
4444
RetainRV,
45+
ClaimRV,
4546
UnsafeClaimRV,
4647
RetainAutorelease,
4748
RetainAutoreleaseRV,
@@ -62,6 +63,7 @@ class ARCRuntimeEntryPoints {
6263
Autorelease = nullptr;
6364
StoreStrong = nullptr;
6465
RetainRV = nullptr;
66+
ClaimRV = nullptr;
6567
UnsafeClaimRV = nullptr;
6668
RetainAutorelease = nullptr;
6769
RetainAutoreleaseRV = nullptr;
@@ -87,6 +89,9 @@ class ARCRuntimeEntryPoints {
8789
case ARCRuntimeEntryPointKind::RetainRV:
8890
return getIntrinsicEntryPoint(RetainRV,
8991
Intrinsic::objc_retainAutoreleasedReturnValue);
92+
case ARCRuntimeEntryPointKind::ClaimRV:
93+
return getIntrinsicEntryPoint(
94+
ClaimRV, Intrinsic::objc_claimAutoreleasedReturnValue);
9095
case ARCRuntimeEntryPointKind::UnsafeClaimRV:
9196
return getIntrinsicEntryPoint(
9297
UnsafeClaimRV, Intrinsic::objc_unsafeClaimAutoreleasedReturnValue);
@@ -126,6 +131,9 @@ class ARCRuntimeEntryPoints {
126131
/// Declaration for objc_retainAutoreleasedReturnValue().
127132
Function *RetainRV = nullptr;
128133

134+
/// Declaration for objc_claimAutoreleasedReturnValue().
135+
Function *ClaimRV = nullptr;
136+
129137
/// Declaration for objc_unsafeClaimAutoreleasedReturnValue().
130138
Function *UnsafeClaimRV = nullptr;
131139

llvm/lib/Transforms/ObjCARC/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -22,5 +22,6 @@ add_llvm_component_library(LLVMObjCARCOpts
2222
Analysis
2323
Core
2424
Support
25+
TargetParser
2526
TransformUtils
2627
)

llvm/lib/Transforms/ObjCARC/ObjCARC.cpp

+29
Original file line numberDiff line numberDiff line change
@@ -101,8 +101,37 @@ BundledRetainClaimRVs::~BundledRetainClaimRVs() {
101101
// can't be tail calls.
102102
if (auto *CI = dyn_cast<CallInst>(CB))
103103
CI->setTailCallKind(CallInst::TCK_NoTail);
104+
105+
// We can also do one final optimization: modify the bundle in the
106+
// annotated call, to change the bundle operand from
107+
// objc_retainAutoreleasedReturnValue
108+
// to:
109+
// objc_claimAutoreleasedReturnValue
110+
// allowing the marker to be omitted from the bundle expansion later.
111+
//
112+
// Note that, confusingly, ClaimRV is semantically equivalent to RetainRV,
113+
// and only differs in that it doesn't require the marker.
114+
// The bundle provides the guarantee that we're emitting the ClaimRV call
115+
// adjacent to the original call, and providing that guarantee is the
116+
// only difference between ClaimRV and RetainRV.
117+
//
118+
// UnsafeClaimRV has a different RC contract entirely.
119+
120+
// Find the clang.arc.attachedcall bundle, and rewrite its operand.
121+
if (UseClaimRV) {
122+
for (auto OBI : CB->bundle_op_infos()) {
123+
auto OBU = CB->operandBundleFromBundleOpInfo(OBI);
124+
if (OBU.getTagID() == LLVMContext::OB_clang_arc_attachedcall &&
125+
OBU.Inputs[0] == EP.get(ARCRuntimeEntryPointKind::RetainRV)) {
126+
CB->setOperand(OBI.Begin,
127+
EP.get(ARCRuntimeEntryPointKind::ClaimRV));
128+
break;
129+
}
130+
}
131+
}
104132
}
105133

134+
// Erase the RV call we emitted earlier: it's already in the bundle.
106135
EraseInstruction(P.first);
107136
}
108137

llvm/lib/Transforms/ObjCARC/ObjCARC.h

+6-1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
#ifndef LLVM_LIB_TRANSFORMS_OBJCARC_OBJCARC_H
2323
#define LLVM_LIB_TRANSFORMS_OBJCARC_OBJCARC_H
2424

25+
#include "ARCRuntimeEntryPoints.h"
2526
#include "llvm/Analysis/ObjCARCAnalysisUtils.h"
2627
#include "llvm/Analysis/ObjCARCUtil.h"
2728
#include "llvm/IR/EHPersonalities.h"
@@ -104,7 +105,9 @@ CallInst *createCallInstWithColors(
104105

105106
class BundledRetainClaimRVs {
106107
public:
107-
BundledRetainClaimRVs(bool ContractPass) : ContractPass(ContractPass) {}
108+
BundledRetainClaimRVs(ARCRuntimeEntryPoints &EP, bool ContractPass,
109+
bool UseClaimRV)
110+
: EP(EP), ContractPass(ContractPass), UseClaimRV(UseClaimRV) {}
108111
~BundledRetainClaimRVs();
109112

110113
/// Insert a retainRV/claimRV call to the normal destination blocks of invokes
@@ -155,7 +158,9 @@ class BundledRetainClaimRVs {
155158
/// A map of inserted retainRV/claimRV calls to annotated calls/invokes.
156159
DenseMap<CallInst *, CallBase *> RVCalls;
157160

161+
ARCRuntimeEntryPoints &EP;
158162
bool ContractPass;
163+
bool UseClaimRV;
159164
};
160165

161166
} // end namespace objcarc

llvm/lib/Transforms/ObjCARC/ObjCARCContract.cpp

+45-1
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
#include "llvm/InitializePasses.h"
4343
#include "llvm/Support/Debug.h"
4444
#include "llvm/Support/raw_ostream.h"
45+
#include "llvm/TargetParser/Triple.h"
4546
#include "llvm/Transforms/ObjCARC.h"
4647

4748
using namespace llvm;
@@ -52,6 +53,11 @@ using namespace llvm::objcarc;
5253
STATISTIC(NumPeeps, "Number of calls peephole-optimized");
5354
STATISTIC(NumStoreStrongs, "Number objc_storeStrong calls formed");
5455

56+
static cl::opt<cl::boolOrDefault> UseObjCClaimRV(
57+
"arc-contract-use-objc-claim-rv",
58+
cl::desc(
59+
"Enable generation of calls to objc_claimAutoreleasedReturnValue"));
60+
5561
//===----------------------------------------------------------------------===//
5662
// Declarations
5763
//===----------------------------------------------------------------------===//
@@ -74,6 +80,9 @@ class ObjCARCContract {
7480
/// A flag indicating whether this optimization pass should run.
7581
bool Run;
7682

83+
/// Whether objc_claimAutoreleasedReturnValue is available.
84+
bool HasClaimRV = false;
85+
7786
/// The inline asm string to insert between calls and RetainRV calls to make
7887
/// the optimization work on targets which need it.
7988
const MDString *RVInstMarker;
@@ -517,6 +526,39 @@ bool ObjCARCContract::tryToPeepholeInstruction(
517526
}
518527
}
519528

529+
/// Should we use objc_claimAutoreleasedReturnValue?
530+
static bool useClaimRuntimeCall(Module &M) {
531+
// Let the flag override our OS-based default.
532+
if (UseObjCClaimRV != cl::BOU_UNSET)
533+
return UseObjCClaimRV == cl::BOU_TRUE;
534+
535+
Triple TT(M.getTargetTriple());
536+
537+
// On x86_64, claimARV doesn't make sense, as the marker isn't actually a nop
538+
// there (it's needed by the calling convention).
539+
if (!TT.isAArch64())
540+
return false;
541+
542+
unsigned Major = TT.getOSMajorVersion();
543+
switch (TT.getOS()) {
544+
default:
545+
return false;
546+
case Triple::IOS:
547+
case Triple::TvOS:
548+
return Major >= 16;
549+
case Triple::WatchOS:
550+
return Major >= 9;
551+
case Triple::BridgeOS:
552+
return Major >= 7;
553+
case Triple::MacOSX:
554+
return Major >= 13;
555+
case Triple::Darwin:
556+
return Major >= 21;
557+
}
558+
559+
return false;
560+
}
561+
520562
//===----------------------------------------------------------------------===//
521563
// Top Level Driver
522564
//===----------------------------------------------------------------------===//
@@ -528,6 +570,8 @@ bool ObjCARCContract::init(Module &M) {
528570

529571
EP.init(&M);
530572

573+
HasClaimRV = useClaimRuntimeCall(M);
574+
531575
// Initialize RVInstMarker.
532576
RVInstMarker = getRVInstMarker(M);
533577

@@ -545,7 +589,7 @@ bool ObjCARCContract::run(Function &F, AAResults *A, DominatorTree *D) {
545589
AA = A;
546590
DT = D;
547591
PA.setAA(A);
548-
BundledRetainClaimRVs BRV(/*ContractPass=*/true);
592+
BundledRetainClaimRVs BRV(EP, /*ContractPass=*/true, HasClaimRV);
549593
BundledInsts = &BRV;
550594

551595
std::pair<bool, bool> R = BundledInsts->insertAfterInvokes(F, DT);

llvm/lib/Transforms/ObjCARC/ObjCARCOpts.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -2423,7 +2423,7 @@ bool ObjCARCOpt::run(Function &F, AAResults &AA) {
24232423
return false;
24242424

24252425
Changed = CFGChanged = false;
2426-
BundledRetainClaimRVs BRV(/*ContractPass=*/false);
2426+
BundledRetainClaimRVs BRV(EP, /*ContractPass=*/false, /*UseClaimRV=*/false);
24272427
BundledInsts = &BRV;
24282428

24292429
LLVM_DEBUG(dbgs() << "<<< ObjCARCOpt: Visiting Function: " << F.getName()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
; RUN: opt -passes=objc-arc-contract -arc-contract-use-objc-claim-rv=1 -S < %s | FileCheck %s --check-prefixes=CHECK,CLAIM
2+
; RUN: opt -passes=objc-arc-contract -arc-contract-use-objc-claim-rv=0 -S < %s | FileCheck %s --check-prefixes=CHECK,RETAIN
3+
4+
; CHECK-LABEL: define void @test0() {
5+
; CLAIM: %[[CALL:.*]] = notail call ptr @foo() [ "clang.arc.attachedcall"(ptr @llvm.objc.claimAutoreleasedReturnValue) ]
6+
; RETAIN: %[[CALL:.*]] = notail call ptr @foo() [ "clang.arc.attachedcall"(ptr @llvm.objc.retainAutoreleasedReturnValue) ]
7+
; CHECK-NEXT: ret void
8+
9+
define void @test0() {
10+
%call1 = call ptr @foo() [ "clang.arc.attachedcall"(ptr @llvm.objc.retainAutoreleasedReturnValue) ]
11+
ret void
12+
}
13+
14+
; CHECK-LABEL: define void @test1() {
15+
; CHECK: %[[CALL:.*]] = notail call ptr @foo() [ "clang.arc.attachedcall"(ptr @llvm.objc.unsafeClaimAutoreleasedReturnValue) ]
16+
; CHECK-NEXT: ret void
17+
18+
define void @test1() {
19+
%call1 = call ptr @foo() [ "clang.arc.attachedcall"(ptr @llvm.objc.unsafeClaimAutoreleasedReturnValue) ]
20+
ret void
21+
}
22+
23+
; CHECK-LABEL: define void @test2() {
24+
; CLAIM: %[[CALL:.*]] = notail call ptr @foo() [ "clang.arc.attachedcall"(ptr @llvm.objc.claimAutoreleasedReturnValue), "otherbundle"() ]
25+
; RETAIN: %[[CALL:.*]] = notail call ptr @foo() [ "clang.arc.attachedcall"(ptr @llvm.objc.retainAutoreleasedReturnValue), "otherbundle"() ]
26+
; CHECK-NEXT: ret void
27+
28+
define void @test2() {
29+
%call1 = call ptr @foo() [ "clang.arc.attachedcall"(ptr @llvm.objc.retainAutoreleasedReturnValue), "otherbundle"() ]
30+
ret void
31+
}
32+
33+
declare ptr @foo()
34+
declare ptr @llvm.objc.retainAutoreleasedReturnValue(ptr)
35+
declare ptr @llvm.objc.unsafeClaimAutoreleasedReturnValue(ptr)

0 commit comments

Comments
 (0)