Skip to content

Commit a8aa6a9

Browse files
committed
Add a cache to remember previously realized classes
1 parent bceeae8 commit a8aa6a9

File tree

5 files changed

+86
-13
lines changed

5 files changed

+86
-13
lines changed

clang/lib/CodeGen/CGObjCMac.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2190,6 +2190,20 @@ CodeGen::RValue CGObjCCommonMac::EmitMessageSend(
21902190
CallSite->setDoesNotReturn();
21912191
}
21922192

2193+
// If this was a class method call on a non-weakly-linked class, record it
2194+
// as realized for the "previously realized" heuristic.
2195+
if (ClassReceiver && Method && !isWeakLinkedClass(ClassReceiver)) {
2196+
if (llvm::BasicBlock *CurrentBB = CGF.Builder.GetInsertBlock())
2197+
// 1. Class methods have forced class realization (regardless direct or
2198+
// not)
2199+
// 2. Direct methods whose receiver is not null means the class is
2200+
// previously realized.
2201+
if (Method->isClassMethod() ||
2202+
(Method->isInstanceMethod() && !ReceiverCanBeNull)) {
2203+
CGF.ObjCRealizedClasses[CurrentBB].insert(ClassReceiver);
2204+
}
2205+
}
2206+
21932207
return nullReturn.complete(CGF, Return, rvalue, ResultType, CallArgs,
21942208
RequiresNullCheck ? Method : nullptr);
21952209
}

clang/lib/CodeGen/CGObjCRuntime.cpp

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -437,16 +437,30 @@ bool CGObjCRuntime::canClassObjectBeUnrealized(
437437
return false;
438438
}
439439

440-
// TODO: Heuristic 3: previously realized
441-
// Walk through previous instructions can be inefficient, since
442-
// `canClassObjectBeUnrealized` is called everytime we emit a class method.
443-
// Besides, a realized subclass means parent class is realized. Therefore,
444-
// a code like below also requires some special handling.
440+
// Heuristic 3: previously realized classes
441+
// If we've already emitted a class method call for this class (or a subclass)
442+
// earlier, then the class must be realized.
445443
//
446-
// ```
447-
// +[Child foo];
448-
// +[Parent foo];
449-
// ```
444+
// TODO: Iter over all dominating blocks instead of just looking at the
445+
// current block. While we can construct a DT using CFG.CurFn, it is expensive
446+
// to do so repeatly when CGF is still emitting blocks.
447+
if (auto *CurBB = CGF.Builder.GetInsertBlock()) {
448+
auto It = CGF.ObjCRealizedClasses.find(CurBB);
449+
if (It != CGF.ObjCRealizedClasses.end()) {
450+
// Check if CalleeClassDecl is the same as or a superclass of any
451+
// realized class in the cache. A realized subclass implies the parent
452+
// is realized.
453+
for (const auto *RealizedClass : It->second) {
454+
if (CalleeClassDecl == RealizedClass)
455+
return false;
456+
if (CalleeClassDecl->isSuperClassOf(RealizedClass)) {
457+
// Also cache this class to reduce future `isSuperClassOf` calls
458+
It->second.insert(CalleeClassDecl);
459+
return false;
460+
}
461+
}
462+
}
463+
}
450464

451465
// Otherwise, assume it can be unrealized.
452466
return true;

clang/lib/CodeGen/CodeGenFunction.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -870,6 +870,15 @@ class CodeGenFunction : public CodeGenTypeCache {
870870
/// rethrows.
871871
SmallVector<llvm::Value *, 8> ObjCEHValueStack;
872872

873+
/// Per-basic-block cache of ObjC classes that have been realized during
874+
/// codegen. When a class method is emitted on a non-weakly-linked class,
875+
/// we record it here. This supports the "previously realized" heuristic
876+
/// in canClassObjectBeUnrealized. The structure supports future
877+
/// dominator-based analysis where we can check dominating blocks.
878+
llvm::DenseMap<llvm::BasicBlock *,
879+
llvm::SmallPtrSet<const ObjCInterfaceDecl *, 4>>
880+
ObjCRealizedClasses;
881+
873882
/// A class controlling the emission of a finally block.
874883
class FinallyInfo {
875884
/// Where the catchall's edge through the cleanup should go.

clang/test/CodeGenObjC/expose-direct-method-opt-class-realization.m

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,3 +128,40 @@ - (int)callerInstanceMethod {
128128
}
129129

130130
@end
131+
132+
// ============================================================================
133+
// HEURISTIC 3: Previously realized classes in the same basic block skip thunk.
134+
// If we've already called a class method (which realizes the class),
135+
// subsequent calls to the same class or its superclasses can skip the thunk.
136+
// ============================================================================
137+
138+
// CHECK-LABEL: define{{.*}} i32 @testPreviouslyRealizedParentClass
139+
int testPreviouslyRealizedParentClass(int flag) {
140+
if (flag) {
141+
// First call to ClassWithoutLoad - needs thunk (class might not be realized)
142+
// CHECK: call i32 @"+[ClassWithoutLoad classDirectMethod]_thunk"(ptr noundef
143+
int a = [ClassWithoutLoad classDirectMethod];
144+
145+
// Second call to same class - should skip thunk (class was just realized)
146+
// CHECK: call i32 @"+[ClassWithoutLoad classDirectMethod]"(ptr noundef
147+
// CHECK-NOT: call i32 @"+[ClassWithoutLoad classDirectMethod]_thunk"
148+
int b = [ClassWithoutLoad classDirectMethod];
149+
150+
// Call to Root (parent of ClassWithoutLoad) - should skip thunk
151+
// because realizing ClassWithoutLoad also realizes its superclass Root.
152+
// CHECK: call i32 @"+[Root rootDirectMethod]"(ptr noundef
153+
// CHECK-NOT: call i32 @"+[Root rootDirectMethod]_thunk"
154+
int c = [Root rootDirectMethod];
155+
return a + b + c;
156+
157+
}
158+
// New block, we are not sure if prev block is executed, so we have to conservatively realize again.
159+
// CHECK: call i32 @"+[ClassWithoutLoad classDirectMethod]_thunk"
160+
// CHECK-NOT: call i32 @"+[ClassWithoutLoad classDirectMethod]"(ptr noundef
161+
int b = [ClassWithoutLoad classDirectMethod];
162+
// CHECK: call i32 @"+[Root rootDirectMethod]"(ptr noundef
163+
// CHECK-NOT: call i32 @"+[Root rootDirectMethod]_thunk"
164+
int c = [Root rootDirectMethod];
165+
166+
return b + c;
167+
}

clang/test/CodeGenObjC/expose-direct-method.m

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -266,12 +266,11 @@ int useSRet(Root *r) {
266266
// TODO: we should know that this instance is non nil.
267267
// CHECK: call void @"-[Root getAggregate]_thunk"
268268
[r getAggregate].a +
269-
// TODO: The compiler is not smart enough to know the class object must be realized yet.
270269
// CHECK-NOT: call i64 @"+[Root classGetComplex]"(ptr noundef
271270
// CHECK: call i64 @"+[Root classGetComplex]_thunk"(ptr noundef
272271
[Root classGetComplex].a +
273-
// CHECK-NOT: call void @"+[Root classGetAggregate]"(ptr {{.*}}sret
274-
// CHECK: call void @"+[Root classGetAggregate]_thunk"(ptr {{.*}}sret
272+
// CHECK-NOT: call void @"+[Root classGetAggregate]_thunk"(ptr {{.*}}sret
273+
// CHECK: call void @"+[Root classGetAggregate]"(ptr {{.*}}sret
275274
[Root classGetAggregate].a
276275
);
277276
}
@@ -291,4 +290,4 @@ int useSRet(Root *r) {
291290
// CHECK: ret void
292291

293292
// CHECK: define {{.*}} @"+[Root classGetComplex]_thunk"
294-
// CHECK: define {{.*}} @"+[Root classGetAggregate]_thunk"
293+
// CHECK-NOT: define {{.*}} @"+[Root classGetAggregate]_thunk"

0 commit comments

Comments
 (0)