Files
llvm-project/clang/test/CodeGenObjC/expose-direct-method.m
Peter Rong 3e2f0bce95 [ObjCDirectPreconditionThunk] precondition check thunk generation (#170618)
## 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`
2026-03-30 10:32:09 -07:00

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: