cache 读写流程
下图是cache的读写流程,当我们读取cache的时候,需要调用cache_getImp
本文的重点在
objc_msgSend,是发起读取cache的地方.
runtime
编译时 顾名思义就是正在编译的时候,就是编译器帮你把源代码翻译成机器能识别的代码。(当然这只是一意义上这么说,实际上可能只是翻译成某个中间状态的语音。)比如静态类型检查之类的。
运行时 顾名思义就是在代码运行起来后,被装载到内存中。(当你的代码保存在磁盘中,没有写入内存之前都是“死”代码,只有写入内存中才变成“活”代码。而且运行时类型检查与编译时类型检查(或者静态类型检查)是不一样的,不是简单的扫描代码,而是在内存中做了一些操作和判断。
详见apple runtime
runtime调用的三种方式:
- Objective-C 方法调用
- NSObject提供的API
- Runtime 底层API,objc_msg_send方法
@interface MLTeacher : NSObject
- (void)sayHello;
@end
@implementation MLTeacher
- (void)sayHello{
NSLog(@"666 %s",__func__);
}
@end
@interface MLPerson : MLTeacher
- (void)sayHello;
- (void)sayNB;
@end
@implementation MLPerson
- (void)sayNB{
NSLog(@"666");
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
MLPerson *person = [MLPerson alloc];
MLTeacher *teach = [MLTeacher alloc];
[person sayNB];
[person sayHello];
NSLog(@"Hello, World!");
}
return 0;
}
通过命令clang -rewrite-objc main.m -o main.cpp 生成cpp文件
可以发现
sayNB之类的方法调用都是通过objc_msgSend来实现的。
那么我们是否能够直接调用objc_msgSend方法呢?我们调用的时候可能会编译失败,需要将Build Settings -> Enable Strict Checking of objc_msgSend Calls 设置成为 NO, 如下图
引入头文件
#import <objc/message.h>代码如下
MLPerson *person = [MLPerson alloc];
objc_msgSend(person, @selector(sayNB));
如果我们重载方法,调用super,cpp的实现会是什么样呢?
static void _I_MLPerson_sayHello(MLPerson * self, SEL _cmd) {
((void (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("MLPerson"))}, sel_registerName("sayHello"));
}
发现cpp的实现是通过调用objc_msgSendSuper实现的.
通过上述的分析,以及对runtime的体验,方法的本质就是objc_msgSend.
objc_msgSend 分析
当在源码中全局搜索objc_msgSend的时候,发现objc_msgSend的实现是汇编
如下是在arm64中objc_msgSend的汇编实现
此时有个疑问是为什么使用汇编实现的,而不是通过c++的代码实现的?
line 54:cmp p0 #0 //p0 是receiver的地址,对p0 与 0 比较,如果为nil直接返回 line 56 或者 line 58
line 60: 将[x0]的内容读取到p13, [x0]是 isa
line 61: p16 赋值为 isa,下图为GetClassFromIsa_p16源码
实际就是指针地址与ISA_MASK, 获取到isa的地址,获取isa的地址是要获取cache, 先从cache中找到IMP.
line 64: CacheLookup
1. .macro CacheLookup Mode, Function, MissLabelDynamic, MissLabelConstant
2.
3. mov x15, x16 // stash the original isa, x16:isa
4. LLookupStart\Function:
5. // p1 = SEL, p16 = isa
6. #if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS
7. ldr p10, [x16, #CACHE] // p10 = mask|buckets, CACHE: 16; p10 = x16 + 16, cache_t
8. lsr p11, p10, #48 // p11 = mask
9. and p10, p10, #0xffffffffffff // p10 = buckets
10. and w12, w1, w11 // x12 = _cmd & mask
11. #elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
12. ldr p11, [x16, #CACHE] // p11 = mask|buckets, CACHE: 16; p10 = x16 + 16, cache_t
13. #if CONFIG_USE_PREOPT_CACHES
14. #if __has_feature(ptrauth_calls)
15. tbnz p11, #0, LLookupPreopt\Function
16. and p10, p11, #0x0000ffffffffffff // p10 = buckets
17. #else
18. and p10, p11, #0x0000fffffffffffe // p10 = buckets
19. tbnz p11, #0, LLookupPreopt\Function
20. #endif
21. eor p12, p1, p1, LSR #7
22. and p12, p12, p11, LSR #48 // x12 = (_cmd ^ (_cmd >> 7)) & mask
23. #else
24. and p10, p11, #0x0000ffffffffffff // p10 = buckets
25. and p12, p1, p11, LSR #48 // x12 = _cmd & mask
26. #endif // CONFIG_USE_PREOPT_CACHES
27. #elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
28. ldr p11, [x16, #CACHE] // p11 = mask|buckets
29. and p10, p11, #~0xf // p10 = buckets
30. and p11, p11, #0xf // p11 = maskShift
31. mov p12, #0xffff
32. lsr p11, p12, p11 // p11 = mask = 0xffff >> p11
33. and p12, p1, p11 // x12 = _cmd & mask
34. #else
35. #error Unsupported cache mask storage for ARM64.
36. #endif
37.
38. add p13, p10, p12, LSL #(1+PTRSHIFT)
39. // p13 = buckets + ((_cmd & mask) << (1+PTRSHIFT))
40.
41. // do {
42. 1: ldp p17, p9, [x13], #-BUCKET_SIZE // {imp, sel} = *bucket--
43. cmp p9, p1 // if (sel != _cmd) {
44. b.ne 3f // scan more
45. // } else {
46. 2: CacheHit \Mode // hit: call or return imp
47. // }
48. 3: cbz p9, \MissLabelDynamic // if (sel == 0) goto Miss;
49. cmp p13, p10 // } while (bucket >= buckets)
50. b.hs 1b
51.
52. // wrap-around:
53. // p10 = first bucket
54. // p11 = mask (and maybe other bits on LP64)
55. // p12 = _cmd & mask
56. //
57. // A full cache can happen with CACHE_ALLOW_FULL_UTILIZATION.
58. // So stop when we circle back to the first probed bucket
59. // rather than when hitting the first bucket again.
60. //
61. // Note that we might probe the initial bucket twice
62. // when the first probed slot is the last entry.
63.
64.
65. #if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS
66. add p13, p10, w11, UXTW #(1+PTRSHIFT)
67. // p13 = buckets + (mask << 1+PTRSHIFT)
68. #elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
69. add p13, p10, p11, LSR #(48 - (1+PTRSHIFT))
70. // p13 = buckets + (mask << 1+PTRSHIFT)
71. // see comment about maskZeroBits
72. #elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
73. add p13, p10, p11, LSL #(1+PTRSHIFT)
74. // p13 = buckets + (mask << 1+PTRSHIFT)
75. #else
76. #error Unsupported cache mask storage for ARM64.
77. #endif
78. add p12, p10, p12, LSL #(1+PTRSHIFT)
79. // p12 = first probed bucket
80.
81. // do {
82. 4: ldp p17, p9, [x13], #-BUCKET_SIZE // {imp, sel} = *bucket--
83. cmp p9, p1 // if (sel == _cmd)
84. b.eq 2b // goto hit
85. cmp p9, #0 // } while (sel != 0 &&
86. ccmp p13, p12, #0, ne // bucket > first_probed)
87. b.hi 4b
88.
89. LLookupEnd\Function:
90. LLookupRecover\Function:
91. b \MissLabelDynamic
92.
93. #if CONFIG_USE_PREOPT_CACHES
94. #if CACHE_MASK_STORAGE != CACHE_MASK_STORAGE_HIGH_16
95. #error config unsupported
96. #endif
97. LLookupPreopt\Function:
98. #if __has_feature(ptrauth_calls)
99. and p10, p11, #0x007ffffffffffffe // p10 = buckets
100. autdb x10, x16 // auth as early as possible
101. #endif
102.
103. // x12 = (_cmd - first_shared_cache_sel)
104. adrp x9, _MagicSelRef@PAGE
105. ldr p9, [x9, _MagicSelRef@PAGEOFF]
106. sub p12, p1, p9
107.
108. // w9 = ((_cmd - first_shared_cache_sel) >> hash_shift & hash_mask)
109. #if __has_feature(ptrauth_calls)
110. // bits 63..60 of x11 are the number of bits in hash_mask
111. // bits 59..55 of x11 is hash_shift
112.
113. lsr x17, x11, #55 // w17 = (hash_shift, ...)
114. lsr w9, w12, w17 // >>= shift
115.
116. lsr x17, x11, #60 // w17 = mask_bits
117. mov x11, #0x7fff
118. lsr x11, x11, x17 // p11 = mask (0x7fff >> mask_bits)
119. and x9, x9, x11 // &= mask
120. #else
121. // bits 63..53 of x11 is hash_mask
122. // bits 52..48 of x11 is hash_shift
123. lsr x17, x11, #48 // w17 = (hash_shift, hash_mask)
124. lsr w9, w12, w17 // >>= shift
125. and x9, x9, x11, LSR #53 // &= mask
126. #endif
127.
128. ldr x17, [x10, x9, LSL #3] // x17 == sel_offs | (imp_offs << 32)
129. cmp x12, w17, uxtw
130.
131. .if \Mode == GETIMP
132. b.ne \MissLabelConstant // cache miss
133. sub x0, x16, x17, LSR #32 // imp = isa - imp_offs
134. SignAsImp x0
135. ret
136. .else
137. b.ne 5f // cache miss
138. sub x17, x16, x17, LSR #32 // imp = isa - imp_offs
139. .if \Mode == NORMAL
140. br x17
141. .elseif \Mode == LOOKUP
142. orr x16, x16, #3 // for instrumentation, note that we hit a constant cache
143. SignAsImp x17
144. ret
145. .else
146. .abort unhandled mode \Mode
147. .endif
148.
149. 5: ldursw x9, [x10, #-8] // offset -8 is the fallback offset
150. add x16, x16, x9 // compute the fallback isa
151. b LLookupStart\Function // lookup again with a new isa
152. .endif
153. #endif // CONFIG_USE_PREOPT_CACHES
154.
155. .endmacro
上述汇编的流程如下:
- Line 3:
x15, x16将isa存储到x15 - Line 12:
ldr p11, [x16, #CACHE], 其中CACHE = 16,p11 = x16+16, 由class结构可知,p11为cache_t的首地址 - Line 18:
and p10, p11, #0x0000fffffffffffe,p10 = p11 & 00x0000fffffffffffe,0x0000fffffffffffe为buckets的掩码, 得到p10 = buckets 首地址. - Line 18:
p11, #0, LLookupPreopt\Function, 不为0就跳转到LLookupPreopt - Line 38:
add p13, p10, p12, LSL #(1+PTRSHIFT),p12为当前sel, imp的index,p13 = buckets + (_cmd & mask) << (1 + PTRSHIFT).PTRSHIFT是3. 右移4位的原因,是要知道buckets内存平移需要平移几个单位. 即buckets[i].p13为当前要查找的bucket. - Line 42 ~ 44:
1:ldp p17, p9, [x13], #-BUCKET_SIZE, 查找到当前位置存不存在bucket. 如果找到就跳转到Cachehit,缓存命中,否则的话,就循环查找. 如果找到,就callimp.
从上述源码中我们可以大概了解到objc_msgSend的流程为如下:
- 通过
receiver获取到isa isa获取cache- 在
cache(包含buckets & mask), 通过buckets的掩码得到buckets. - 通过
mask掩码得到mask(mask为当前buckets的capacipty -1). - 对
sel imp进行hash, 得到第一次查找的index buckets + index整个缓存中的第几个bucket- 得到查找到的
sel imp与传进来的sel == _cmd进行比较, 如果相等,缓存命中,调用imp - 如果不想等,
buckets + index --,向前查找,跳转到step 7 - 如果整个
buckets遍历结束,都没有找到, 跳转到objc_msgSend_uncached objc_msgSend_uncached会在后续的文章更新...
补充
bt:查看堆栈