aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPeter Rong <PeterRong@meta.com>2025-12-03 22:45:48 -0800
committerPeter Rong <PeterRong@meta.com>2025-12-03 22:45:48 -0800
commitc2be4fc58ae66d1040deab63002a9b09a12987fe (patch)
tree269a05a583f8d9934746f50c48ffab50cc1ee3f0
parent110198ae0bd837972d4d9ff013494fbf3206b7a4 (diff)
downloadllvm-users/DataCorrupted/ExposeDirectMethod-tests.tar.gz
llvm-users/DataCorrupted/ExposeDirectMethod-tests.tar.bz2
llvm-users/DataCorrupted/ExposeDirectMethod-tests.zip
-rw-r--r--clang/test/CodeGenObjC/direct-method-ret-mismatch.m24
-rw-r--r--clang/test/CodeGenObjC/expose-direct-method-consumed.m119
-rw-r--r--clang/test/CodeGenObjC/expose-direct-method-linkedlist.m136
-rw-r--r--clang/test/CodeGenObjC/expose-direct-method-opt-class-realization.m148
-rw-r--r--clang/test/CodeGenObjC/expose-direct-method-varargs.m103
-rw-r--r--clang/test/CodeGenObjC/expose-direct-method-visibility-linkage.m170
-rw-r--r--clang/test/CodeGenObjC/expose-direct-method.m295
7 files changed, 995 insertions, 0 deletions
diff --git a/clang/test/CodeGenObjC/direct-method-ret-mismatch.m b/clang/test/CodeGenObjC/direct-method-ret-mismatch.m
index 889a6d68da0d..8399b8133afa 100644
--- a/clang/test/CodeGenObjC/direct-method-ret-mismatch.m
+++ b/clang/test/CodeGenObjC/direct-method-ret-mismatch.m
@@ -1,19 +1,43 @@
// RUN: %clang_cc1 -emit-llvm -fobjc-arc -triple x86_64-apple-darwin10 %s -o - | FileCheck %s
+// RUN: %clang_cc1 -emit-llvm -fobjc-arc -triple arm64-apple-darwin10 -fobjc-expose-direct-methods %s -o - | FileCheck %s --check-prefix=EXPOSE-DIRECT
+
__attribute__((objc_root_class))
@interface Root
- (Root *)method __attribute__((objc_direct));
@end
+// EXPOSE-DIRECT-LABEL: define ptr @useMethod
+Root* useMethod(Root *root) {
+ // EXPOSE-DIRECT: call ptr @"-[Root method]_thunk"
+ return [root method];
+}
+
@implementation Root
// CHECK-LABEL: define internal ptr @"\01-[Root something]"(
+// EXPOSE-DIRECT-LABEL: define internal ptr @"\01-[Root something]"(ptr noundef
- (id)something {
// CHECK: %{{[^ ]*}} = call {{.*}} @"\01-[Root method]"
return [self method];
}
// CHECK-LABEL: define hidden ptr @"\01-[Root method]"(
+// EXPOSE-DIRECT-LABEL: define hidden ptr @"-[Root method]"(ptr noundef
- (id)method {
return self;
}
+
@end
+
+// New thunk will be emitted after [Root method] instead of useMethod because its been updatd with method.
+// EXPOSE-DIRECT-LABEL: define linkonce_odr hidden ptr @"-[Root method]_thunk"
+// EXPOSE-DIRECT-LABEL: entry:
+// EXPOSE-DIRECT: %[[IS_NIL:.*]] = icmp eq ptr {{.*}}, null
+// EXPOSE-DIRECT: br i1 %[[IS_NIL]], label %objc_direct_method.self_is_nil, label %objc_direct_method.cont
+// EXPOSE-DIRECT-LABEL: objc_direct_method.self_is_nil:
+// EXPOSE-DIRECT: call void @llvm.memset.p0.i64
+// EXPOSE-DIRECT: br label %dummy_ret_block
+// EXPOSE-DIRECT-LABEL: objc_direct_method.cont:
+// EXPOSE-DIRECT: %[[RET:.*]] = musttail call ptr @"-[Root method]"
+// EXPOSE-DIRECT: ret ptr %[[RET]]
+// EXPOSE-DIRECT-LABEL: dummy_ret_block:
diff --git a/clang/test/CodeGenObjC/expose-direct-method-consumed.m b/clang/test/CodeGenObjC/expose-direct-method-consumed.m
new file mode 100644
index 000000000000..e039baccb1c5
--- /dev/null
+++ b/clang/test/CodeGenObjC/expose-direct-method-consumed.m
@@ -0,0 +1,119 @@
+// REQUIRES: system-darwin
+
+// RUN: mkdir -p %t
+
+// RUN: %clang -Xclang -fobjc-expose-direct-methods \
+// RUN: -target arm64-apple-darwin -fobjc-arc \
+// RUN: -O0 -S -emit-llvm %s -o -
+
+// RUN: %clang -fobjc-expose-direct-methods \
+// RUN: -target arm64-apple-darwin -fobjc-arc \
+// RUN: -O2 -S -emit-llvm %s -o -
+//
+// | FileCheck %s --check-prefix=OPT
+
+// RUN: %clang -Xclang -fobjc-expose-direct-methods \
+// RUN: -target arm64-apple-darwin -fobjc-arc \
+// RUN: -O2 -framework Foundation %s -o %t/shape
+
+// RUN: %t/shape 1 2 3 4 | FileCheck %s --check-prefix=EXE
+
+// OPT: adlfk
+#import <Foundation/Foundation.h>
+#include "math.h"
+
+@interface Shape: NSObject
+@property(direct, readonly) int x;
+@property(direct, readonly) int y;
+@property(direct) Shape* innerShape;
+@property(class) int numInstances;
+@property(direct) int instanceId;
+- (void) dealloc;
+- (instancetype)initWithX:(int)x Y:(int)y __attribute__((objc_direct));
+- (instancetype)initDefault __attribute__((objc_direct));
+- (double) distanceFrom: (Shape *) __attribute__((ns_consumed)) s __attribute__((objc_direct));
++ (Shape *) default __attribute__((objc_direct));
+- (instancetype) clone __attribute__((objc_direct));
+@end
+
+@implementation Shape
+@dynamic numInstances;
+static int numInstances=0;
+
+- (void) dealloc {
+ printf("Dealloc %d\n", self.instanceId);
+}
+- (instancetype)initWithX:(int)x Y:(int)y {
+ if (self = [super init]) {
+ _x = x;
+ _y = y;
+ _innerShape = nil;
+ _instanceId = numInstances;
+ printf("Alloc %d\n", _instanceId);
+ numInstances++;
+ }
+ return self;
+}
+
+- (instancetype)initDefault {
+ return [self initWithX:0 Y:0];
+}
+
++ (Shape*) default {
+ return [[Shape alloc] initDefault];
+}
+
+
+- (instancetype) clone {
+ return [[Shape alloc] initWithX:self.x Y:self.y];
+}
+
+- (double) distanceFrom:(Shape *) __attribute__((ns_consumed)) s __attribute__((objc_direct)) {
+ double dist = sqrt((s.x - self.x) * (s.x - self.x) + (s.y - self.y) * (s.y - self.y));
+ return dist;
+}
+@end
+
+int main(int argc, char** argv) { // argv = ["1", "2", "3", "4"]
+@autoreleasepool {
+ // EXE: Alloc
+ Shape* classDefault = [Shape default];
+ // EXE-NEXT: Alloc
+ Shape* s = [[Shape alloc] initWithX:atoi(argv[0]) Y:atoi(argv[1])];
+ // EXE-NEXT: Alloc
+ Shape* t = [[Shape alloc] initWithX:atoi(argv[2]) Y:atoi(argv[3])];
+ // EXE-NEXT: Alloc
+ Shape* zero = [[Shape alloc] initDefault];
+ // EXE-NEXT: Alloc
+ Shape* anotherDefault = [Shape default];
+
+ // EXE-NEXT: Alloc
+ Shape* cloned = [s clone];
+
+ Shape* null = nil;
+ // EXE: Dist: 2.82
+ printf("Dist: %lf\n", [s distanceFrom:t]);
+ // EXE-NEXT: Dist: 3.60
+ printf("Dist: %lf\n", [zero distanceFrom:t]);
+ // EXE-NEXT: Dist: 3.60
+ printf("Dist: %lf\n", [classDefault distanceFrom:t]);
+ // EXE-NEXT: Dist: 0.00
+ printf("Dist: %lf\n", [s distanceFrom:s]);
+ // EXE-NEXT: Dist: 0.00
+ printf("Dist: %lf\n", [classDefault distanceFrom:anotherDefault]);
+ // EXE-NEXT: Dist: 0.00
+ printf("Dist: %lf\n", [null distanceFrom:zero]);
+ // EXE-NEXT: Dist: 0.00
+ printf("Dist: %lf\n", [s distanceFrom:cloned]);
+
+ // Five shapes are allocated.
+ // EXE: Dealloc
+ // EXE-NEXT: Dealloc
+ // EXE-NEXT: Dealloc
+ // EXE-NEXT: Dealloc
+ // EXE-NEXT: Dealloc
+ // EXE-NEXT: Dealloc
+ // EXE-NOT: Dealloc
+}
+ return 0;
+}
diff --git a/clang/test/CodeGenObjC/expose-direct-method-linkedlist.m b/clang/test/CodeGenObjC/expose-direct-method-linkedlist.m
new file mode 100644
index 000000000000..0a2e2f8d2b1a
--- /dev/null
+++ b/clang/test/CodeGenObjC/expose-direct-method-linkedlist.m
@@ -0,0 +1,136 @@
+// REQUIRES: system-darwin
+
+// RUN: mkdir -p %t
+
+// RUN: %clang -fobjc-expose-direct-methods \
+// RUN: -target arm64-apple-darwin -fobjc-arc \
+// RUN: -O2 -framework Foundation %s -o %t/thunk-linkedlist
+
+// RUN: %t/thunk-linkedlist 8 7 6 | FileCheck %s --check-prefix=EXE
+#import <Foundation/Foundation.h>
+
+@interface LinkedList: NSObject
+@property(direct, readonly, nonatomic) int v;
+@property(direct, strong, nonatomic) LinkedList* next;
+@property(direct, readonly, nonatomic) int instanceId;
+@property(strong, nonatomic, direct) void ( ^ printBlock )( void );
+@property(class) int numInstances;
+
+// Prints instantceId before dealloc
+- (void) dealloc;
+- (instancetype)initWithV:(int)v Next:(id)next __attribute__((objc_direct));
+- (instancetype)clone __attribute__((objc_direct));
+- (void)print __attribute__((objc_direct));
+- (instancetype) reverseWithPrev:(id) prev __attribute__((objc_direct));
+- (int) size __attribute__((objc_direct));
+- (int) sum __attribute__((objc_direct));
+- (double) avg __attribute__((objc_direct));
+@end
+
+@implementation LinkedList
+@dynamic numInstances;
+static int numInstances=0;
+
+- (void) dealloc {
+ printf("Dealloc id: %d\n", self.instanceId);
+}
+
+- (instancetype)initWithV:(int)v Next:(id)next{
+ if (self = [super init]) {
+ _v = v;
+ _next = next;
+ _instanceId = numInstances;
+ LinkedList* __weak weakSelf = self;
+ _printBlock = ^void(void) { [weakSelf print]; };
+ numInstances++;
+ printf("Alloc id: %d, v: %d\n", self.instanceId, self.v);
+ }
+ return self;
+}
+- (instancetype) clone {
+ return [[LinkedList alloc] initWithV:self.v Next:[self.next clone]];
+}
+
+- (void) print {
+ printf("id: %d, v: %d\n", self.instanceId, self.v);
+ [self.next print];
+}
+
+
+- (LinkedList*) reverseWithPrev:(LinkedList*) prev{
+ LinkedList* newHead = (self.next == nil) ? self : [self.next reverseWithPrev:self];
+ self.next = prev;
+ return newHead;
+}
+
+- (int) size {
+ return 1 + [self.next size];
+}
+- (int) sum {
+ return self.v + [self.next sum];
+}
+- (double) avg {
+ return (double)[self sum] / (double)[self size];
+}
+@end
+
+int main(int argc, char** argv) { // argv = ["8", "7", "6"]
+@autoreleasepool {
+ // CHECK: call ptr @"-[LinkedList initWithV:Next:]_thunk"
+ // CHECK: call ptr @"-[LinkedList initWithV:Next:]_thunk"
+ LinkedList* ll = [[LinkedList alloc] initWithV:atoi(argv[1]) Next:[[LinkedList alloc] initWithV:atoi(argv[2]) Next:nil]];
+ // EXE: Alloc id: 0, v: 7
+ // EXE: Alloc id: 1, v: 8
+
+ // CHECK: call ptr @"-[LinkedList initWithV:Next:]_thunk"
+ // CHECK: call ptr @"-[LinkedList next]_thunk"
+ // CHECK: call void @"-[LinkedList setNext:]_thunk"
+ ll.next.next = [[LinkedList alloc] initWithV:atoi(argv[3]) Next:nil];
+ // EXE: Alloc id: 2, v: 6
+
+ // CHECK: call void @"-[LinkedList print]_thunk"
+ [ll print];
+ // EXE: id: 1, v: 8
+ // EXE: id: 0, v: 7
+ // EXE: id: 2, v: 6
+
+ // CHECK: call ptr @"-[LinkedList clone]_thunk"
+ LinkedList* cloned = [ll clone];
+ // Because of the recursive clone, the tail is allocated first.
+ // EXE: Alloc id: 3, v: 6
+ // EXE: Alloc id: 4, v: 7
+ // EXE: Alloc id: 5, v: 8
+
+ // CHECK: call void @"-[LinkedList print]_thunk"
+ [cloned print];
+ // EXE: id: 5, v: 8
+ // EXE: id: 4, v: 7
+ // EXE: id: 3, v: 6
+
+ // CHECK: call ptr @"-[LinkedList printBlock]_thunk"
+ cloned.printBlock();
+ // EXE: id: 5, v: 8
+ // EXE: id: 4, v: 7
+ // EXE: id: 3, v: 6
+
+
+ // CHECK: call ptr @"-[LinkedList reverseWithPrev:]_thunk"
+ ll = [ll reverseWithPrev:nil];
+ // EXE: id: 2, v: 6
+ // EXE: id: 0, v: 7
+ // EXE: id: 1, v: 8
+
+ // CHECK: call void @"-[LinkedList print]_thunk"
+ [ll print];
+
+ // All objects should be deallocated.
+ // EXE: Dealloc
+ // EXE-NEXT: Dealloc
+ // EXE-NEXT: Dealloc
+ // EXE-NEXT: Dealloc
+ // EXE-NEXT: Dealloc
+ // EXE-NEXT: Dealloc
+ // EXE-NOT: Dealloc
+}
+ return 0;
+}
diff --git a/clang/test/CodeGenObjC/expose-direct-method-opt-class-realization.m b/clang/test/CodeGenObjC/expose-direct-method-opt-class-realization.m
new file mode 100644
index 000000000000..34f8537d7556
--- /dev/null
+++ b/clang/test/CodeGenObjC/expose-direct-method-opt-class-realization.m
@@ -0,0 +1,148 @@
+// RUN: %clang_cc1 -emit-llvm -fobjc-arc -triple arm64-apple-darwin10 \
+// RUN: -fobjc-expose-direct-methods %s -o - | FileCheck %s
+
+// ============================================================================
+// HEURISTIC 1: Classes with +load method skip thunk for class methods
+// because they are guaranteed to be realized when the binary is loaded.
+// ============================================================================
+
+__attribute__((objc_root_class))
+@interface ClassWithLoad
++ (void)load;
++ (int)classDirectMethod __attribute__((objc_direct));
+@end
+
+@implementation ClassWithLoad
+
++ (void)load {
+ // This method causes the class to be realized at load time
+}
+
+// CHECK-LABEL: define hidden i32 @"+[ClassWithLoad classDirectMethod]"(ptr noundef %self)
++ (int)classDirectMethod {
+ return 42;
+}
+
+@end
+
+// A class without +load method for comparison
+__attribute__((objc_root_class))
+@interface ClassWithoutLoad
++ (int)classDirectMethod __attribute__((objc_direct));
+@end
+
+@implementation ClassWithoutLoad
+
+// CHECK-LABEL: define hidden i32 @"+[ClassWithoutLoad classDirectMethod]"(ptr noundef %self)
++ (int)classDirectMethod {
+ return 42;
+}
+
+@end
+
+// CHECK-LABEL: define{{.*}} i32 @testClassWithLoad()
+int testClassWithLoad(void) {
+ // Because ClassWithLoad has +load, it's guaranteed to be realized.
+ // So we should call the implementation directly, NOT through a thunk.
+ //
+ // CHECK: call i32 @"+[ClassWithLoad classDirectMethod]"(ptr noundef
+ // CHECK-NOT: call i32 @"+[ClassWithLoad classDirectMethod]_thunk"
+ return [ClassWithLoad classDirectMethod];
+}
+
+// CHECK-LABEL: define{{.*}} i32 @testClassWithoutLoad()
+int testClassWithoutLoad(void) {
+ // ClassWithoutLoad has no +load, so the class might not be realized.
+ // We need to call through the thunk which will realize the class.
+ //
+ // CHECK: call i32 @"+[ClassWithoutLoad classDirectMethod]_thunk"(ptr noundef
+ return [ClassWithoutLoad classDirectMethod];
+}
+
+// ============================================================================
+// HEURISTIC 2: Calls from within the same class skip thunk
+// because if we're executing a method of the class, it must be realized.
+// ============================================================================
+
+__attribute__((objc_root_class))
+@interface SameClassTest
++ (int)classDirectMethod __attribute__((objc_direct));
++ (int)callerClassMethod __attribute__((objc_direct));
+- (int)callerInstanceMethod __attribute__((objc_direct));
+@end
+
+@implementation SameClassTest
+
+// CHECK-LABEL: define hidden i32 @"+[SameClassTest classDirectMethod]"(ptr noundef %self)
++ (int)classDirectMethod {
+ return 42;
+}
+
+// CHECK-LABEL: define hidden i32 @"+[SameClassTest callerClassMethod]"(ptr noundef %self)
++ (int)callerClassMethod {
+ // Calling a class method from another class method of the SAME class.
+ // The class must be realized (we're already executing a method of it).
+ // Should call implementation directly, NOT through thunk.
+ //
+ // CHECK: call i32 @"+[SameClassTest classDirectMethod]"(ptr noundef
+ // CHECK-NOT: call i32 @"+[SameClassTest classDirectMethod]_thunk"
+ return [SameClassTest classDirectMethod];
+}
+
+// CHECK-LABEL: define hidden i32 @"-[SameClassTest callerInstanceMethod]"(ptr noundef %self)
+- (int)callerInstanceMethod {
+ // Calling a class method from an instance method of the SAME class.
+ // The class must be realized (we're already executing a method of it).
+ // Should call implementation directly, NOT through thunk.
+ //
+ // CHECK: call i32 @"+[SameClassTest classDirectMethod]"(ptr noundef
+ // CHECK-NOT: call i32 @"+[SameClassTest classDirectMethod]_thunk"
+ return [SameClassTest classDirectMethod];
+}
+
+@end
+
+__attribute__((objc_root_class))
+@interface SuperClass
++ (int)superClassMethod __attribute__((objc_direct));
+@end
+
+@implementation SuperClass
+
+// CHECK-LABEL: define hidden i32 @"+[SuperClass superClassMethod]"(ptr noundef %self)
++ (int)superClassMethod {
+ return 100;
+}
+
+@end
+
+@interface SubClass : SuperClass
++ (int)subCallerMethod __attribute__((objc_direct));
+- (int)subInstanceCaller __attribute__((objc_direct));
+@end
+
+@implementation SubClass
+
+// CHECK-LABEL: define hidden i32 @"+[SubClass subCallerMethod]"(ptr noundef %self)
++ (int)subCallerMethod {
+ // Calling a superclass's class method from a subclass method.
+ // SuperClass must be realized because SubClass inherits from it.
+ // Should call implementation directly, NOT through thunk.
+ //
+ // CHECK: call i32 @"+[SuperClass superClassMethod]"(ptr noundef
+ // CHECK-NOT: call i32 @"+[SuperClass superClassMethod]_thunk"
+ return [SuperClass superClassMethod];
+}
+
+// CHECK-LABEL: define hidden i32 @"-[SubClass subInstanceCaller]"(ptr noundef %self)
+- (int)subInstanceCaller {
+ // Calling a superclass's class method from a subclass instance method.
+ // SuperClass must be realized because SubClass inherits from it.
+ // Should call implementation directly, NOT through thunk.
+ //
+ // CHECK: call i32 @"+[SuperClass superClassMethod]"(ptr noundef
+ // CHECK-NOT: call i32 @"+[SuperClass superClassMethod]_thunk"
+ return [SuperClass superClassMethod];
+}
+
+@end
diff --git a/clang/test/CodeGenObjC/expose-direct-method-varargs.m b/clang/test/CodeGenObjC/expose-direct-method-varargs.m
new file mode 100644
index 000000000000..84388470df5f
--- /dev/null
+++ b/clang/test/CodeGenObjC/expose-direct-method-varargs.m
@@ -0,0 +1,103 @@
+// Test variadic direct methods - should get exposed symbols but not use thunks
+// RUN: %clang_cc1 -emit-llvm -fobjc-arc -triple arm64-apple-darwin10 \
+// RUN: -fobjc-expose-direct-methods %s -o - | FileCheck %s
+
+__attribute__((objc_root_class))
+@interface Root
+- (int)varMethod:(int)first, ... __attribute__((objc_direct));
++ (void)printf:(Root *)format, ... __attribute__((objc_direct));
+@end
+
+// Add a weakly linked class and a weakly linked class method
+__attribute__((objc_root_class, weak_import))
+@interface WeakRoot
++ (int)weakPrintf:(int)first, ... __attribute__((objc_direct, weak_import));
+@end
+
+
+@implementation Root
+
+// Variadic methods get exposed symbols WITHOUT nil checks in implementation
+// The caller will emit inline nil checks instead of using thunks
+// CHECK-LABEL: define hidden i32 @"-[Root varMethod:]"(
+// CHECK-NOT: @"\01-[Root varMethod:]"
+// CHECK-NOT: @"-[Root varMethod:]_thunk"
+- (int)varMethod:(int)first, ... {
+ // Should NOT have nil check (moved to caller)
+ // CHECK-NOT: icmp eq ptr {{.*}}, null
+ // CHECK-NOT: objc_direct_method.self_is_nil
+ return first;
+}
+
+// CHECK-LABEL: define hidden void @"+[Root printf:]"(
+// CHECK-NOT: @"\01+[Root printf:]"
+// CHECK-NOT: @"+[Root printf:]_thunk"
++ (void)printf:(Root *)format, ... {}
+
+@end
+
+// Test: Nullable receiver should have inline nil check
+// CHECK-LABEL: define{{.*}} void @useRoot(
+void useRoot(Root *_Nullable root) {
+ // For nullable receivers, we should emit nil check inline
+ // CHECK: icmp eq ptr %{{[0-9]+}}, null
+ // CHECK: br i1 %{{[0-9]+}}, label %msgSend.null-receiver, label %msgSend.call
+
+ // CHECK: msgSend.call:
+ // CHECK: call i32 (ptr, i32, ...) @"-[Root varMethod:]"(ptr noundef %{{[0-9]+}}, i32 noundef 1, i32 noundef 2, double noundef 3.0{{.*}})
+ // CHECK: br label %msgSend.cont
+
+ // CHECK: msgSend.null-receiver:
+ // CHECK: br label %msgSend.cont
+
+ // CHECK: msgSend.cont:
+ [root varMethod:1, 2, 3.0];
+
+ // Class realization before call
+ // CHECK: %{{.*}} = load ptr, ptr @"OBJC_CLASSLIST_REFERENCES_$
+ // CHECK: %{{.*}} = load ptr, ptr @OBJC_SELECTOR_REFERENCES_,
+ // CHECK: %{{.*}} = call ptr @objc_msgSend
+ // CHECK: call void (ptr, ptr, ...) @"+[Root printf:]"(
+ [Root printf:root, "hello", root];
+
+ // For weakly linked class, inline realization first
+ // CHECK: %{{.*}} = load ptr, ptr @"OBJC_CLASSLIST_REFERENCES_$
+ // CHECK: %{{.*}} = load ptr, ptr @OBJC_SELECTOR_REFERENCES_,
+ // CHECK: %{{.*}} = call ptr @objc_msgSend
+
+ // Then perform nil check
+ // CHECK: %{{.*}} = icmp eq ptr %{{.*}}, null
+ // CHECK: br i1 %{{.*}}, label %msgSend.null-receiver{{.*}}, label %msgSend.call{{.*}}
+
+ // Finally call the class method
+ // CHECK: %{{.*}} = call i32 (ptr, i32, ...) @"+[WeakRoot weakPrintf:]"
+ [WeakRoot weakPrintf: 1, 2, 3.0];
+}
+
+// Test: Non-null receiver
+// NOTE: Phase 7 will optimize this to skip nil checks when _Nonnull is detected
+// For now (Phase 4), it should look the same as the nullable receiver case above
+
+// CHECK-LABEL: define{{.*}} void @useRootNonNull(
+void useRootNonNull(Root *_Nonnull root) {
+ // For nullable receivers, we should emit nil check inline
+ // CHECK: icmp eq ptr %{{[0-9]+}}, null
+ // CHECK: br i1 %{{[0-9]+}}, label %msgSend.null-receiver, label %msgSend.call
+
+ // CHECK: msgSend.call:
+ // CHECK: call i32 (ptr, i32, ...) @"-[Root varMethod:]"(ptr noundef %{{[0-9]+}}, i32 noundef 1, i32 noundef 2, double noundef 3.0{{.*}})
+ // CHECK: br label %msgSend.cont
+
+ // CHECK: msgSend.null-receiver:
+ // CHECK: br label %msgSend.cont
+
+ // CHECK: msgSend.cont:
+ [root varMethod:1, 2, 3.0];
+
+ // Class realization before call
+ // CHECK: %{{.*}} = load ptr, ptr @"OBJC_CLASSLIST_REFERENCES_$
+ // CHECK: %{{.*}} = load ptr, ptr @OBJC_SELECTOR_REFERENCES_,
+ // CHECK: %{{.*}} = call ptr @objc_msgSend
+ // CHECK: call void (ptr, ptr, ...) @"+[Root printf:]"(
+ [Root printf:root, "hello", root];
+}
diff --git a/clang/test/CodeGenObjC/expose-direct-method-visibility-linkage.m b/clang/test/CodeGenObjC/expose-direct-method-visibility-linkage.m
new file mode 100644
index 000000000000..1f2e6e36245d
--- /dev/null
+++ b/clang/test/CodeGenObjC/expose-direct-method-visibility-linkage.m
@@ -0,0 +1,170 @@
+// REQUIRES: system-darwin
+
+// RUN: rm -rf %t
+// RUN: split-file %s %t
+
+// Test 1: Check IR from library implementation (visibility attributes)
+// RUN: %clang -target arm64-apple-darwin -fobjc-arc \
+// RUN: -fobjc-expose-direct-methods -S -emit-llvm -o - %t/foo.m \
+// RUN: -I %t | FileCheck %s --check-prefix=FOO_M
+
+// Test 2: Check IR from main (consumer)
+// RUN: %clang -target arm64-apple-darwin -fobjc-arc \
+// RUN: -fobjc-expose-direct-methods -S -emit-llvm -o - %t/main.m \
+// RUN: -I %t | FileCheck %s --check-prefix=MAIN_M
+
+// Test 3: Build libFoo.dylib from foo.m
+// RUN: %clang -fobjc-expose-direct-methods -target arm64-apple-darwin \
+// RUN: -fobjc-arc -dynamiclib %t/foo.m -I %t \
+// RUN: -framework Foundation \
+// RUN: -install_name @rpath/libFoo.dylib \
+// RUN: -o %t/libFoo.dylib
+
+// Test 4: Verify visibility works correctly in dylib
+// RUN: llvm-nm -g %t/libFoo.dylib | FileCheck %s --check-prefix=DYLIB
+
+// Hidden visibility methods should NOT be exported
+// DYLIB-NOT: -[Foo instanceMethod:]
+// DYLIB-NOT: +[Foo classMethod:]
+// DYLIB-NOT: -[Foo privateValue:]
+// DYLIB-NOT: -[Foo setPrivateValue:]
+
+// Default visibility methods SHOULD be exported
+// DYLIB: {{[0-9a-f]+}} T _+[Foo exportedClassMethod:]
+// DYLIB: {{[0-9a-f]+}} T _-[Foo exportedInstanceMethod:]
+// DYLIB: {{[0-9a-f]+}} T _-[Foo exportedValue]
+// DYLIB: {{[0-9a-f]+}} T _-[Foo setExportedValue:]
+
+// Test 5: Compile main.m
+// RUN: %clang -fobjc-expose-direct-methods -target arm64-apple-darwin \
+// RUN: -fobjc-arc -c %t/main.m -I %t -o %t/main.o
+
+// Test 6: Link main with libFoo.dylib
+// RUN: %clang -target arm64-apple-darwin -fobjc-arc \
+// RUN: %t/main.o -L%t -lFoo \
+// RUN: -Wl,-rpath,%t \
+// RUN: -framework Foundation \
+// RUN: -o %t/main
+
+// Test 7: Verify symbols in main executable
+// RUN: llvm-nm %t/main | FileCheck %s --check-prefix=EXE
+
+// Thunks should be defined locally
+// EXE-DAG: {{[0-9a-f]+}} t _-[Foo exportedInstanceMethod:]_thunk
+// EXE-DAG: {{[0-9a-f]+}} t _-[Foo exportedValue]_thunk
+// EXE-DAG: {{[0-9a-f]+}} t _-[Foo setExportedValue:]_thunk
+
+// Actual methods should be undefined
+// EXE-DAG: U _+[Foo exportedClassMethod:]
+// EXE-DAG: U _-[Foo exportedInstanceMethod:]
+// EXE-DAG: U _-[Foo exportedValue]
+// EXE-DAG: U _-[Foo setExportedValue:]
+
+//--- foo.h
+// Header for libFoo
+#import <Foundation/Foundation.h>
+
+@interface Foo : NSObject
+// Direct properties with default hidden visibility
+@property (nonatomic, direct) int privateValue;
+
+// Direct properties with explicit default visibility
+@property (nonatomic, direct) int exportedValue __attribute__((visibility("default")));
+
+- (instancetype)initWithprivateValue:(int)x exportedValue:(int)y;
+// Default hidden visibility
+- (int)instanceMethod:(int)x __attribute__((objc_direct));
++ (int)classMethod:(int)x __attribute__((objc_direct));
+
+// Explicit default visibility (should be exported)
+- (int)exportedInstanceMethod:(int)x __attribute__((objc_direct, visibility("default")));
++ (int)exportedClassMethod:(int)x __attribute__((objc_direct, visibility("default")));
+@end
+
+//--- foo.m
+
+// libFoo does not have thunks because the true implementation is not used internally.
+// FOO_M-NOT: @{{.*}}_thunk
+#import "foo.h"
+
+@implementation Foo
+
+// FOO_M-LABEL: define internal ptr @"\01-[Foo initWithprivateValue:exportedValue:]"
+- (instancetype)initWithprivateValue:(int)x exportedValue:(int)y {
+ self = [super init];
+ if (self) {
+ _privateValue = x;
+ _exportedValue = y;
+ }
+ return self;
+}
+
+// FOO_M-LABEL: define hidden i32 @"-[Foo instanceMethod:]"
+- (int)instanceMethod:(int)x {
+ // Compiler is smart enough to know that self is non-nil, so we dispatch to
+ // true implementation.
+ // FOO_M: call i32 @"-[Foo privateValue]"
+ // FOO_M: call i32 @"-[Foo exportedValue]"
+ return x + [self privateValue] + [self exportedValue];
+}
+
+// Hidden property getter and setter (default visibility)
+// FOO_M-LABEL: define hidden i32 @"-[Foo privateValue]"
+
+// Exported property getter and setter (explicit default visibility)
+// FOO_M-LABEL: define dso_local i32 @"-[Foo exportedValue]"
+
+// FOO_M-LABEL: define hidden i32 @"+[Foo classMethod:]"
++ (int)classMethod:(int)x {
+ return x * 3;
+}
+
+// FOO_M-LABEL: define dso_local i32 @"-[Foo exportedInstanceMethod:]"
+- (int)exportedInstanceMethod:(int)x {
+ // FOO_M: call i32 @"-[Foo privateValue]"
+ // FOO_M: call i32 @"-[Foo exportedValue]"
+ return x + [self privateValue] + [self exportedValue];
+}
+
+// FOO_M-LABEL: define dso_local i32 @"+[Foo exportedClassMethod:]"
++ (int)exportedClassMethod:(int)x {
+ return x * 5;
+}
+
+// Hidden property getter and setter (default visibility)
+// FOO_M-LABEL: define hidden void @"-[Foo setPrivateValue:]"
+
+// Exported property getter and setter (explicit default visibility)
+// FOO_M-LABEL: define dso_local void @"-[Foo setExportedValue:]"
+
+@end
+
+//--- main.m
+// Consumer of libFoo (separate linkage unit)
+#import "foo.h"
+#include <stdio.h>
+
+int main() {
+@autoreleasepool {
+ Foo *obj = [[Foo alloc] initWithprivateValue:10 exportedValue:20];
+ printf("Allocated Foo\n");
+
+ // MAIN_M: call void @"-[Foo setExportedValue:]_thunk"
+ [obj setExportedValue:30];
+
+ // MAIN_M: call i32 @"-[Foo exportedValue]_thunk"
+ printf("Reset exportedValue: %d\n", [obj exportedValue]);
+
+ // MAIN_M: call i32 @"-[Foo exportedInstanceMethod:]_thunk"
+ printf("Exported instance: %d\n", [obj exportedInstanceMethod:10]);
+
+ // MAIN_M: call i32 @"+[Foo exportedClassMethod:]"
+ printf("Exported class: %d\n", [Foo exportedClassMethod:10]);
+}
+ return 0;
+}
+
+// Thunks are generated during compilation
+// MAIN_M-LABEL: define linkonce_odr hidden void @"-[Foo setExportedValue:]_thunk"
+// MAIN_M-LABEL: define linkonce_odr hidden i32 @"-[Foo exportedValue]_thunk"
+// MAIN_M-LABEL: define linkonce_odr hidden i32 @"-[Foo exportedInstanceMethod:]_thunk"
diff --git a/clang/test/CodeGenObjC/expose-direct-method.m b/clang/test/CodeGenObjC/expose-direct-method.m
new file mode 100644
index 000000000000..a5b85121774c
--- /dev/null
+++ b/clang/test/CodeGenObjC/expose-direct-method.m
@@ -0,0 +1,295 @@
+// This test consolidates tests for basic functionality, stub dispatch, and thunk generation
+// RUN: %clang_cc1 -emit-llvm -fobjc-arc -triple arm64-apple-darwin10 \
+// RUN: -fobjc-expose-direct-methods %s -o - | FileCheck %s
+
+struct my_complex_struct {
+ int a, b;
+};
+
+struct my_aggregate_struct {
+ int a, b;
+ char buf[128];
+};
+
+__attribute__((objc_root_class))
+@interface Root
+- (int)getInt __attribute__((objc_direct));
+@property(direct, readonly) int intProperty;
+@property(direct, readonly) int intProperty2;
+@property(direct, readonly) id objectProperty;
+@end
+
+@implementation Root
+// CHECK-LABEL: define hidden i32 @"-[Root intProperty2]"(ptr noundef %self)
+- (int)intProperty2 {
+ return 42;
+}
+
+// CHECK-LABEL: define hidden i32 @"-[Root getInt]"(ptr noundef %self)
+- (int)getInt __attribute__((objc_direct)) {
+ return 42;
+}
+
+// CHECK-LABEL: define hidden i32 @"+[Root classGetInt]"(ptr noundef %self)
++ (int)classGetInt __attribute__((objc_direct)) {
+ return 42;
+}
+
+// CHECK-LABEL: define hidden i64 @"-[Root getComplex]"(ptr noundef %self)
+- (struct my_complex_struct)getComplex __attribute__((objc_direct)) {
+ struct my_complex_struct st = {.a = 42};
+ return st;
+}
+
+// CHECK-LABEL: define hidden i64 @"+[Root classGetComplex]"(ptr noundef %self)
++ (struct my_complex_struct)classGetComplex __attribute__((objc_direct)) {
+ struct my_complex_struct st = {.a = 42};
+ return st;
+}
+
+// CHECK-LABEL: define hidden void @"-[Root getAggregate]"(ptr {{.*}} sret(%struct.my_aggregate_struct) {{.*}} %agg.result, ptr noundef %self)
+- (struct my_aggregate_struct)getAggregate __attribute__((objc_direct)) {
+ struct my_aggregate_struct st = {.a = 42};
+ return st;
+}
+
+// CHECK-LABEL: define hidden void @"+[Root classGetAggregate]"(ptr {{.*}} sret(%struct.my_aggregate_struct) {{.*}} %agg.result, ptr noundef %self)
++ (struct my_aggregate_struct)classGetAggregate __attribute__((objc_direct)) {
+ struct my_aggregate_struct st = {.a = 42};
+ return st;
+}
+
+// CHECK-LABEL: define hidden void @"-[Root accessCmd]"(ptr noundef %self)
+- (void)accessCmd __attribute__((objc_direct)) {
+ // loading the _cmd selector
+ SEL sel = _cmd;
+}
+
+@end
+
+// CHECK-LABEL: define hidden i32 @"-[Root intProperty]"(ptr noundef %self)
+// CHECK-LABEL: define hidden ptr @"-[Root objectProperty]"(ptr noundef %self)
+
+@interface Foo : Root {
+ id __strong _cause_cxx_destruct;
+}
+@property(nonatomic, readonly, direct) int getDirect_setDynamic;
+@property(nonatomic, readonly) int getDynamic_setDirect;
+@end
+
+@interface Foo ()
+@property(nonatomic, readwrite) int getDirect_setDynamic;
+@property(nonatomic, readwrite, direct) int getDynamic_setDirect;
+- (int)directMethodInExtension __attribute__((objc_direct));
+@end
+
+@interface Foo (Cat)
+- (int)directMethodInCategory __attribute__((objc_direct));
+@end
+
+__attribute__((objc_direct_members))
+@implementation Foo
+// CHECK-LABEL: define hidden i32 @"-[Foo directMethodInExtension]"(ptr noundef %self)
+- (int)directMethodInExtension {
+ return 42;
+}
+
+// CHECK-LABEL: define hidden i32 @"-[Foo getDirect_setDynamic]"(ptr noundef %self)
+// CHECK-LABEL: define internal void @"\01-[Foo setGetDirect_setDynamic:]"(ptr noundef %self, ptr noundef %_cmd, i32 noundef %getDirect_setDynamic)
+// CHECK-LABEL: define internal i32 @"\01-[Foo getDynamic_setDirect]"(ptr noundef %self, ptr noundef %_cmd)
+// CHECK-LABEL: define hidden void @"-[Foo setGetDynamic_setDirect:]"(ptr noundef %self, i32 noundef %getDynamic_setDirect)
+
+@end
+
+@implementation Foo (Cat)
+// CHECK-LABEL: define hidden i32 @"-[Foo directMethodInCategory]"(ptr noundef %self)
+- (int)directMethodInCategory {
+ return 42;
+}
+
+// CHECK-LABEL: define hidden i32 @"-[Foo directMethodInCategoryNoDecl]"(ptr noundef %self)
+- (int)directMethodInCategoryNoDecl __attribute__((objc_direct)) {
+ return 42;
+}
+
+@end
+
+// CHECK-LABEL: define{{.*}} i32 @useRoot(ptr noundef %r)
+int useRoot(Root *r) {
+ // CHECK: call i32 @"-[Root getInt]_thunk"(ptr noundef %{{[0-9]+}})
+ // CHECK: call i32 @"-[Root intProperty]_thunk"(ptr noundef %{{[0-9]+}})
+ // CHECK: call i32 @"-[Root intProperty2]_thunk"(ptr noundef %{{[0-9]+}})
+ return [r getInt] + [r intProperty] + [r intProperty2];
+}
+
+// Thunks are emitted after the first function that uses them
+// CHECK-LABEL: define linkonce_odr hidden i32 @"-[Root getInt]_thunk"(ptr noundef %self)
+// CHECK: entry:
+// CHECK: %[[IS_NIL:.*]] = icmp eq ptr {{.*}}, null
+// CHECK: br i1 %[[IS_NIL]], label %objc_direct_method.self_is_nil, label %objc_direct_method.cont
+// CHECK: objc_direct_method.self_is_nil:
+// CHECK: call void @llvm.memset
+// CHECK: br label %dummy_ret_block
+// CHECK: objc_direct_method.cont:
+// CHECK: %[[RET:.*]] = musttail call i32 @"-[Root getInt]"(ptr noundef %self)
+// CHECK: ret i32 %[[RET]]
+// CHECK: dummy_ret_block:
+
+// CHECK-LABEL: define linkonce_odr hidden i32 @"-[Root intProperty]_thunk"(ptr noundef %self)
+// CHECK: entry:
+// CHECK: %[[IS_NIL:.*]] = icmp eq ptr {{.*}}, null
+// CHECK: br i1 %[[IS_NIL]], label %objc_direct_method.self_is_nil, label %objc_direct_method.cont
+// CHECK: objc_direct_method.self_is_nil:
+// CHECK: call void @llvm.memset
+// CHECK: br label %dummy_ret_block
+// CHECK: objc_direct_method.cont:
+// CHECK: %[[RET:.*]] = musttail call i32 @"-[Root intProperty]"(ptr noundef %self)
+// CHECK: ret i32 %[[RET]]
+// CHECK: dummy_ret_block:
+
+// CHECK-LABEL: define linkonce_odr hidden i32 @"-[Root intProperty2]_thunk"(ptr noundef %self)
+// CHECK: entry:
+// CHECK: %[[IS_NIL:.*]] = icmp eq ptr {{.*}}, null
+// CHECK: br i1 %[[IS_NIL]], label %objc_direct_method.self_is_nil, label %objc_direct_method.cont
+// CHECK: objc_direct_method.self_is_nil:
+// CHECK: call void @llvm.memset
+// CHECK: br label %dummy_ret_block
+// CHECK: objc_direct_method.cont:
+// CHECK: %[[RET:.*]] = musttail call i32 @"-[Root intProperty2]"(ptr noundef %self)
+// CHECK: ret i32 %[[RET]]
+// CHECK: dummy_ret_block:
+
+// CHECK-LABEL: define{{.*}} i32 @useFoo(ptr noundef %f)
+int useFoo(Foo *f) {
+ // CHECK: call void @"-[Foo setGetDynamic_setDirect:]_thunk"(ptr noundef %{{[0-9]+}}, i32 noundef 1)
+ // CHECK: call i32 @"-[Foo getDirect_setDynamic]_thunk"(ptr noundef %{{[0-9]+}})
+ // CHECK: call i32 @"-[Foo directMethodInExtension]_thunk"(ptr noundef %{{[0-9]+}})
+ // CHECK: call i32 @"-[Foo directMethodInCategory]_thunk"(ptr noundef %{{[0-9]+}})
+ // CHECK: call i32 @"-[Foo directMethodInCategoryNoDecl]_thunk"(ptr noundef %{{[0-9]+}})
+ [f setGetDynamic_setDirect:1];
+ return [f getDirect_setDynamic] +
+ [f directMethodInExtension] +
+ [f directMethodInCategory] +
+ [f directMethodInCategoryNoDecl];
+}
+
+// CHECK-LABEL: define linkonce_odr hidden void @"-[Foo setGetDynamic_setDirect:]_thunk"(ptr noundef %self, i32 noundef
+// CHECK: entry:
+// CHECK: %[[IS_NIL:.*]] = icmp eq ptr {{.*}}, null
+// CHECK: br i1 %[[IS_NIL]], label %objc_direct_method.self_is_nil, label %objc_direct_method.cont
+// CHECK: objc_direct_method.self_is_nil:
+// CHECK: br label %dummy_ret_block
+// CHECK: objc_direct_method.cont:
+// CHECK: musttail call void @"-[Foo setGetDynamic_setDirect:]"(ptr noundef %self, i32 noundef
+// CHECK: ret void
+// CHECK: dummy_ret_block:
+// CHECK: ret void
+
+// CHECK-LABEL: define linkonce_odr hidden i32 @"-[Foo getDirect_setDynamic]_thunk"(ptr noundef %self)
+// CHECK: entry:
+// CHECK: %[[IS_NIL:.*]] = icmp eq ptr {{.*}}, null
+// CHECK: br i1 %[[IS_NIL]], label %objc_direct_method.self_is_nil, label %objc_direct_method.cont
+// CHECK: objc_direct_method.self_is_nil:
+// CHECK: call void @llvm.memset
+// CHECK: br label %dummy_ret_block
+// CHECK: objc_direct_method.cont:
+// CHECK: %[[RET:.*]] = musttail call i32 @"-[Foo getDirect_setDynamic]"(ptr noundef %self)
+// CHECK: ret i32 %[[RET]]
+// CHECK: dummy_ret_block:
+
+// CHECK-LABEL: define linkonce_odr hidden i32 @"-[Foo directMethodInExtension]_thunk"(ptr noundef %self)
+// CHECK: entry:
+// CHECK: %[[IS_NIL:.*]] = icmp eq ptr {{.*}}, null
+// CHECK: br i1 %[[IS_NIL]], label %objc_direct_method.self_is_nil, label %objc_direct_method.cont
+// CHECK: objc_direct_method.self_is_nil:
+// CHECK: call void @llvm.memset
+// CHECK: br label %dummy_ret_block
+// CHECK: objc_direct_method.cont:
+// CHECK: %[[RET:.*]] = musttail call i32 @"-[Foo directMethodInExtension]"(ptr noundef %self)
+// CHECK: ret i32 %[[RET]]
+// CHECK: dummy_ret_block:
+
+// CHECK-LABEL: define linkonce_odr hidden i32 @"-[Foo directMethodInCategory]_thunk"(ptr noundef %self)
+// CHECK: entry:
+// CHECK: %[[IS_NIL:.*]] = icmp eq ptr {{.*}}, null
+// CHECK: br i1 %[[IS_NIL]], label %objc_direct_method.self_is_nil, label %objc_direct_method.cont
+// CHECK: objc_direct_method.self_is_nil:
+// CHECK: call void @llvm.memset
+// CHECK: br label %dummy_ret_block
+// CHECK: objc_direct_method.cont:
+// CHECK: %[[RET:.*]] = musttail call i32 @"-[Foo directMethodInCategory]"(ptr noundef %self)
+// CHECK: ret i32 %[[RET]]
+// CHECK: dummy_ret_block:
+
+// CHECK-LABEL: define linkonce_odr hidden i32 @"-[Foo directMethodInCategoryNoDecl]_thunk"(ptr noundef %self)
+// CHECK: entry:
+// CHECK: %[[IS_NIL:.*]] = icmp eq ptr {{.*}}, null
+// CHECK: br i1 %[[IS_NIL]], label %objc_direct_method.self_is_nil, label %objc_direct_method.cont
+// CHECK: objc_direct_method.self_is_nil:
+// CHECK: call void @llvm.memset
+// CHECK: br label %dummy_ret_block
+// CHECK: objc_direct_method.cont:
+// CHECK: %[[RET:.*]] = musttail call i32 @"-[Foo directMethodInCategoryNoDecl]"(ptr noundef %self)
+// CHECK: ret i32 %[[RET]]
+// CHECK: dummy_ret_block:
+
+__attribute__((objc_root_class))
+@interface RootDeclOnly
+@property(direct, readonly) int intProperty;
+@end
+
+// CHECK-LABEL: define{{.*}} i32 @useRootDeclOnly(ptr noundef %r)
+int useRootDeclOnly(RootDeclOnly *r) {
+ // CHECK: call i32 @"-[RootDeclOnly intProperty]_thunk"(ptr noundef %{{[0-9]+}})
+ return [r intProperty];
+}
+
+// Verify thunk is generated for external direct method
+// CHECK: declare{{.*}} i32 @"-[RootDeclOnly intProperty]"(ptr)
+// CHECK-LABEL: define linkonce_odr hidden i32 @"-[RootDeclOnly intProperty]_thunk"(ptr %self)
+// CHECK: entry:
+// CHECK: %[[IS_NIL:.*]] = icmp eq ptr {{.*}}, null
+// CHECK: br i1 %[[IS_NIL]], label %objc_direct_method.self_is_nil, label %objc_direct_method.cont
+// CHECK: objc_direct_method.self_is_nil:
+// CHECK: call void @llvm.memset
+// CHECK: br label %dummy_ret_block
+// CHECK: objc_direct_method.cont:
+// CHECK: %[[RET:.*]] = musttail call i32 @"-[RootDeclOnly intProperty]"(ptr noundef %self)
+// CHECK: ret i32 %[[RET]]
+// CHECK: dummy_ret_block:
+
+int useSRet(Root *r) {
+ return (
+ // First call is to instance method - uses thunk
+ // CHECK: call i64 @"-[Root getComplex]_thunk"
+ [r getComplex].a +
+ // TODO: we should know that this instance is non nil.
+ // CHECK: call void @"-[Root getAggregate]_thunk"
+ [r getAggregate].a +
+ // After the instance method call, Root class is realized
+ // So subsequent class method calls can skip the thunk and call directly
+ // CHECK: call i64 @"+[Root classGetComplex]"(ptr noundef
+ [Root classGetComplex].a +
+ // CHECK: call void @"+[Root classGetAggregate]"(ptr {{.*}}sret
+ [Root classGetAggregate].a
+ );
+}
+
+// CHECK-LABEL: define linkonce_odr hidden i64 @"-[Root getComplex]_thunk"
+// CHECK-LABEL: define linkonce_odr hidden void @"-[Root getAggregate]_thunk"(ptr dead_on_unwind noalias writable sret(%struct.my_aggregate_struct) {{.*}} %agg.result, ptr noundef %self)
+// CHECK: entry:
+// CHECK: %[[IS_NIL:.*]] = icmp eq ptr {{.*}}, null
+// CHECK: br i1 %[[IS_NIL]], label %objc_direct_method.self_is_nil, label %objc_direct_method.cont
+// CHECK: objc_direct_method.self_is_nil:
+// CHECK: call void @llvm.memset
+// CHECK: br label %dummy_ret_block
+// CHECK: objc_direct_method.cont:
+// CHECK: musttail call void @"-[Root getAggregate]"(ptr dead_on_unwind writable sret(%struct.my_aggregate_struct) {{.*}} %agg.result, ptr noundef %self)
+// CHECK: ret void
+// CHECK: dummy_ret_block:
+// CHECK: ret void
+
+// The class method thunk is NOT generated because useSRet calls instance methods first,
+// which realizes the class, so class methods can be called directly.
+// CHECK-NOT: define {{.*}} @"+[Root classGetComplex]_thunk"
+// CHECK-NOT: define {{.*}} @"+[Root classGetAggregate]_thunk"