diff options
| author | Peter Rong <PeterRong@meta.com> | 2025-12-03 22:45:48 -0800 |
|---|---|---|
| committer | Peter Rong <PeterRong@meta.com> | 2025-12-03 22:45:48 -0800 |
| commit | c2be4fc58ae66d1040deab63002a9b09a12987fe (patch) | |
| tree | 269a05a583f8d9934746f50c48ffab50cc1ee3f0 | |
| parent | 110198ae0bd837972d4d9ff013494fbf3206b7a4 (diff) | |
| download | llvm-users/DataCorrupted/ExposeDirectMethod-tests.tar.gz llvm-users/DataCorrupted/ExposeDirectMethod-tests.tar.bz2 llvm-users/DataCorrupted/ExposeDirectMethod-tests.zip | |
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" |
