## TL;DR This is a stack of PRs implementing features to expose direct methods ABI. You can see the RFC, design, and discussion [here](https://discourse.llvm.org/t/rfc-optimizing-code-size-of-objc-direct-by-exposing-function-symbols-and-moving-nil-checks-to-thunks/88866). https://github.com/llvm/llvm-project/pull/170616 Flag `-fobjc-direct-precondition-thunk` set up https://github.com/llvm/llvm-project/pull/170617 Code refactoring to ease later reviews https://github.com/llvm/llvm-project/pull/170618 **Thunk generation** https://github.com/llvm/llvm-project/pull/170619 Optimizations, some class objects can be known to be realized ## Implementation details ### Dispatching - `GetDirectMethodCallee` handles the dispatching logic. Previously we only need to call `GenerateDirectMethod` to get the declaration of a direct method. - `GenerateDirectMethod` first attempts to acquire the declaration of the implementation, and return it if the flag is not set. - Generate and return thunk if we can't dispatch to true implementation (i.e. we can't reason receiver is def not null or class object is not realized) ### Precondition check thunk generation - `GenerateObjCDirectThunk` generates the thunk, it is called on demand by `GetDirectMethodCallee` - Thunk inherits all attributes from the true implementation, see `StartObjCDirectThunk` for more detail. - `StartObjCDirectThunk` and `FinishObjCDirectThunk` follows the design pattern of `StartThunk` and `FinishThunk` in CGVTable. ### Precondition check inline generation - If the function need to have precondition check inlined (`shouldHaveNilCheckInline`), caller will emit the nil check during `EmitMessageSend` - Class realization is generated inline - No extra nil check is generated - we reuse `NullReturnState` to emit the nil check for us, it already emits nil check at caller side to handle `ns_consumed`, we just need to tell `NullReturnState` to do the work by setting the flag `RequiresNullCheck |= ReceiverCanBeNull;` ### Visibility and linkage - Visibility is still by default `Hidden`. But `StartObjCMethod` will now respect source level visibility attributes so methods with `__attribute((visibility("default"))` can be used in other linking units - Linkage is by default `External` ## Tests - `expose-direct-method.m` follow the example of `direct-method.m` - `direct-method-ret-mismatch.m` make sure we can handle the corner case - `expose-direct-method-consumed.m ` and `expose-direct-method-linkedlist.m` executable test on Mac only to validate ARC correctness - `expose-direct-method-varargs.m` - `expose-direct-method-visibility-linkage.m`
331 lines
13 KiB
Objective-C
331 lines
13 KiB
Objective-C
// RUN: %clang_cc1 -emit-llvm -fobjc-arc -triple arm64-apple-darwin10 \
|
|
// RUN: -fobjc-direct-precondition-thunk %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]D"(ptr noundef %self)
|
|
- (int)intProperty2 {
|
|
return 42;
|
|
}
|
|
|
|
// CHECK-LABEL: define hidden i32 @"-[Root getInt]D"(ptr noundef %self)
|
|
- (int)getInt __attribute__((objc_direct)) {
|
|
return 42;
|
|
}
|
|
|
|
// CHECK-LABEL: define hidden i32 @"+[Root classGetInt]D"(ptr noundef %self)
|
|
+ (int)classGetInt __attribute__((objc_direct)) {
|
|
return 42;
|
|
}
|
|
|
|
// CHECK-LABEL: define hidden i64 @"-[Root getComplex]D"(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]D"(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]D"(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]D"(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]D"(ptr noundef %self)
|
|
- (void)accessCmd __attribute__((objc_direct)) {
|
|
// loading the _cmd selector
|
|
SEL sel = _cmd;
|
|
}
|
|
|
|
@end
|
|
|
|
// CHECK-LABEL: define hidden i32 @"-[Root intProperty]D"(ptr noundef %self)
|
|
// CHECK-LABEL: define hidden ptr @"-[Root objectProperty]D"(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]D"(ptr noundef %self)
|
|
- (int)directMethodInExtension {
|
|
return 42;
|
|
}
|
|
|
|
// CHECK-LABEL: define hidden i32 @"-[Foo getDirect_setDynamic]D"(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:]D"(ptr noundef %self, i32 noundef %getDynamic_setDirect)
|
|
|
|
@end
|
|
|
|
@implementation Foo (Cat)
|
|
// CHECK-LABEL: define hidden i32 @"-[Foo directMethodInCategory]D"(ptr noundef %self)
|
|
- (int)directMethodInCategory {
|
|
return 42;
|
|
}
|
|
|
|
// CHECK-LABEL: define hidden i32 @"-[Foo directMethodInCategoryNoDecl]D"(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]D_thunk"(ptr noundef %{{[0-9]+}})
|
|
// CHECK: call i32 @"-[Root intProperty]D_thunk"(ptr noundef %{{[0-9]+}})
|
|
// CHECK: call i32 @"-[Root intProperty2]D_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]D_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]D"(ptr noundef %self)
|
|
// CHECK: ret i32 %[[RET]]
|
|
// CHECK: dummy_ret_block:
|
|
|
|
// CHECK-LABEL: define linkonce_odr hidden i32 @"-[Root intProperty]D_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]D"(ptr noundef %self)
|
|
// CHECK: ret i32 %[[RET]]
|
|
// CHECK: dummy_ret_block:
|
|
|
|
// CHECK-LABEL: define linkonce_odr hidden i32 @"-[Root intProperty2]D_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]D"(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:]D_thunk"(ptr noundef %{{[0-9]+}}, i32 noundef 1)
|
|
// CHECK: call i32 @"-[Foo getDirect_setDynamic]D_thunk"(ptr noundef %{{[0-9]+}})
|
|
// CHECK: call i32 @"-[Foo directMethodInExtension]D_thunk"(ptr noundef %{{[0-9]+}})
|
|
// CHECK: call i32 @"-[Foo directMethodInCategory]D_thunk"(ptr noundef %{{[0-9]+}})
|
|
// CHECK: call i32 @"-[Foo directMethodInCategoryNoDecl]D_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:]D_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:]D"(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]D_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]D"(ptr noundef %self)
|
|
// CHECK: ret i32 %[[RET]]
|
|
// CHECK: dummy_ret_block:
|
|
|
|
// CHECK-LABEL: define linkonce_odr hidden i32 @"-[Foo directMethodInExtension]D_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]D"(ptr noundef %self)
|
|
// CHECK: ret i32 %[[RET]]
|
|
// CHECK: dummy_ret_block:
|
|
|
|
// CHECK-LABEL: define linkonce_odr hidden i32 @"-[Foo directMethodInCategory]D_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]D"(ptr noundef %self)
|
|
// CHECK: ret i32 %[[RET]]
|
|
// CHECK: dummy_ret_block:
|
|
|
|
// CHECK-LABEL: define linkonce_odr hidden i32 @"-[Foo directMethodInCategoryNoDecl]D_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]D"(ptr noundef %self)
|
|
// CHECK: ret i32 %[[RET]]
|
|
// CHECK: dummy_ret_block:
|
|
|
|
__attribute__((objc_root_class))
|
|
@interface RootDeclOnly
|
|
@property(direct, readonly) int intProperty;
|
|
@end
|
|
|
|
__attribute__((objc_root_class, weak_import))
|
|
@interface WeakRoot
|
|
+ (int)classGetInt __attribute__((objc_direct, weak_import));
|
|
@end
|
|
|
|
// CHECK-LABEL: define{{.*}} i32 @useRootDeclOnly(ptr noundef %r)
|
|
int useRootDeclOnly(RootDeclOnly *r) {
|
|
// CHECK: call i32 @"-[RootDeclOnly intProperty]D_thunk"(ptr noundef %{{[0-9]+}})
|
|
return [r intProperty];
|
|
}
|
|
|
|
// Verify thunk is generated for external direct method
|
|
// CHECK: declare{{.*}} i32 @"-[RootDeclOnly intProperty]D"(ptr)
|
|
// CHECK-LABEL: define linkonce_odr hidden i32 @"-[RootDeclOnly intProperty]D_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 @"-[RootDeclOnly intProperty]D"(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]D_thunk"
|
|
[r getComplex].a +
|
|
// TODO: we should know that this instance is non nil.
|
|
// CHECK: call void @"-[Root getAggregate]D_thunk"
|
|
[r getAggregate].a +
|
|
// TODO: The compiler is not smart enough to know the class object must be realized yet.
|
|
// CHECK: call i64 @"+[Root classGetComplex]D_thunk"(ptr noundef
|
|
[Root classGetComplex].a +
|
|
// CHECK: call void @"+[Root classGetAggregate]D_thunk"(ptr {{.*}}sret
|
|
[Root classGetAggregate].a
|
|
);
|
|
}
|
|
|
|
// CHECK-LABEL: define linkonce_odr hidden i64 @"-[Root getComplex]D_thunk"
|
|
// CHECK-LABEL: define linkonce_odr hidden void @"-[Root getAggregate]D_thunk"(ptr dead_on_unwind 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]D"(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
|
|
|
|
// Class method thunks should have class realization (objc_msgSend)
|
|
// instead of a nil check.
|
|
// CHECK-LABEL: define linkonce_odr hidden i64 @"+[Root classGetComplex]D_thunk"(ptr noundef %self)
|
|
// CHECK: entry:
|
|
// CHECK-NOT: icmp eq ptr
|
|
// CHECK: call ptr @objc_msgSend
|
|
// CHECK: musttail call i64 @"+[Root classGetComplex]D"(ptr noundef %self)
|
|
// CHECK: ret i64
|
|
|
|
// CHECK-LABEL: define linkonce_odr hidden void @"+[Root classGetAggregate]D_thunk"(ptr {{.*}} sret(%struct.my_aggregate_struct) {{.*}} %agg.result, ptr noundef %self)
|
|
// CHECK: entry:
|
|
// CHECK-NOT: icmp eq ptr
|
|
// CHECK: call ptr @objc_msgSend
|
|
// CHECK: musttail call void @"+[Root classGetAggregate]D"(ptr {{.*}} sret(%struct.my_aggregate_struct) {{.*}} %agg.result, ptr noundef %self)
|
|
// CHECK: ret void
|
|
|
|
// CHECK-LABEL: define{{.*}} i32 @useWeakRoot()
|
|
int useWeakRoot() {
|
|
// CHECK: call i32 @"+[WeakRoot classGetInt]D_thunk"
|
|
return [WeakRoot classGetInt];
|
|
}
|
|
|
|
// Weakly linked class method thunks should have class realization
|
|
// AND a nil check (the weak class may not exist at runtime).
|
|
// CHECK-LABEL: define linkonce_odr hidden i32 @"+[WeakRoot classGetInt]D_thunk"(ptr noundef %self)
|
|
// CHECK: entry:
|
|
// CHECK: call ptr @objc_msgSend
|
|
// 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 @"+[WeakRoot classGetInt]D"(ptr noundef %self)
|
|
// CHECK: ret i32 %[[RET]]
|
|
// CHECK: dummy_ret_block:
|