llvm-project/clang/test/CodeGenObjC/expose-direct-method-linkedlist.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

99 lines
2.9 KiB
Objective-C

// RUN: %clang_cc1 -emit-llvm -fobjc-arc -fblocks -fobjc-runtime-has-weak \
// RUN: -triple arm64-apple-macos11.0 \
// RUN: -fobjc-direct-precondition-thunk %s -o - | FileCheck %s
#define nil ((id)0)
__attribute__((objc_root_class))
@interface LinkedList
@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 );
+ (instancetype)alloc;
- (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));
- (int) sumWith:(LinkedList *) __attribute__((ns_consumed)) other __attribute__((objc_direct));
@end
@implementation LinkedList
static int numInstances=0;
- (instancetype)initWithV:(int)v Next:(id)next{
_v = v;
_next = next;
_instanceId = numInstances;
LinkedList* __weak weakSelf = self;
_printBlock = ^void(void) { [weakSelf print]; };
numInstances++;
return self;
}
- (instancetype) clone {
return [[LinkedList alloc] initWithV:self.v Next:[self.next clone]];
}
- (void) print {
[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];
}
- (int) sumWith:(LinkedList *) __attribute__((ns_consumed)) other {
return [self sum] + [other sum];
}
@end
int main() {
// CHECK: call ptr @"-[LinkedList initWithV:Next:]D_thunk"
// CHECK: call ptr @"-[LinkedList initWithV:Next:]D_thunk"
LinkedList* ll = [[LinkedList alloc] initWithV:8 Next:[[LinkedList alloc] initWithV:7 Next:nil]];
// CHECK: call ptr @"-[LinkedList initWithV:Next:]D_thunk"
// CHECK: call ptr @"-[LinkedList next]D_thunk"
// CHECK: call void @"-[LinkedList setNext:]D_thunk"
ll.next.next = [[LinkedList alloc] initWithV:6 Next:nil];
// CHECK: call void @"-[LinkedList print]D_thunk"
[ll print];
// CHECK: call ptr @"-[LinkedList clone]D_thunk"
LinkedList* cloned = [ll clone];
// CHECK: call void @"-[LinkedList print]D_thunk"
[cloned print];
// CHECK: call ptr @"-[LinkedList printBlock]D_thunk"
cloned.printBlock();
// Test ns_consumed parameter with direct method thunk.
// CHECK: call i32 @"-[LinkedList sumWith:]D_thunk"
int combined = [ll sumWith:[cloned clone]];
// CHECK: call ptr @"-[LinkedList reverseWithPrev:]D_thunk"
ll = [ll reverseWithPrev:nil];
// CHECK: call void @"-[LinkedList print]D_thunk"
[ll print];
return 0;
}