上篇objc_msgSend
汇编分析得到了class
,接着开始查找缓存的分析
CacheLookup汇编分析
//CacheLookup LOOKUP, _objc_msgLookup, __objc_msgLookup_uncached
.macro CacheLookup Mode, Function, MissLabelDynamic, MissLabelConstant
//
// Restart protocol:
//
// As soon as we're past the LLookupStart\Function label we may have
// loaded an invalid cache pointer or mask.
//
// When task_restartable_ranges_synchronize() is called,
// (or when a signal hits us) before we're past LLookupEnd\Function,
// then our PC will be reset to LLookupRecover\Function which forcefully
// jumps to the cache-miss codepath which have the following
// requirements:
//
// GETIMP:
// The cache-miss is just returning NULL (setting x0 to 0)
//
// NORMAL and LOOKUP:
// - x0 contains the receiver
// - x1 contains the selector
// - x16 contains the isa
// - other registers are set as per calling conventions
//
//将class移到x15里面
mov x15, x16 // stash the original isa
LLookupStart\Function:
// p1 = SEL, p16 = isa
//TARGET_OS_OSX/TARGET_OS_SIMULATOR
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS
ldr p10, [x16, #CACHE] // p10 = mask|buckets
lsr p11, p10, #48 // p11 = mask
and p10, p10, #0xffffffffffff // p10 = buckets
and w12, w1, w11 // x12 = _cmd & mask
//真机arm64
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
//#define CACHE (2 * __SIZEOF_POINTER__)
//[x16, #CACHE]将x16(isa)平移CACHE大小 (CACHE是2倍指针的大小就是16)得到cache_t
//ldr p11, cache_t 将cache_t放到p11,即p11 = cache_t
ldr p11, [x16, #CACHE] // p11 = mask|buckets
//CONFIG_USE_PREOPT_CACHES:arm64 && ios && 不是模拟器 && 不是mac
#if CONFIG_USE_PREOPT_CACHES
//iphoneX及以后设备
#if __has_feature(ptrauth_calls)
//将p11与0比较,如果不为0就执行LLookupPreopt\Function
tbnz p11, #0, LLookupPreopt\Function
//将p11(cache_t)的首地址 & 0x0000ffffffffffff 得到buckets 给 p10
and p10, p11, #0x0000ffffffffffff // p10 = buckets
#else
//将p11(cache_t)的首地址 & 0x0000fffffffffffe 得到buckets 给 p10
and p10, p11, #0x0000fffffffffffe // p10 = buckets
//将p11与0比较,如果不为0就执行LLookupPreopt\Function
tbnz p11, #0, LLookupPreopt\Function
#endif
//下面是p11为0的操作
//p0是receiver,p1是sel
//eor 逻辑异或(^)
//将p1(sel)右移7个位置后 ^ sel后,给p12
eor p12, p1, p1, LSR #7
//p11(cache_t)首地址右移48位后 & p12 后 再给到p12
and p12, p12, p11, LSR #48 // x12 = (_cmd ^ (_cmd >> 7)) & mask
#else
and p10, p11, #0x0000ffffffffffff // p10 = buckets
// p11, LSR #48 p11(cache_t)首地址右移48位后得到mask
//p1(sel) & mask 得到index 存到p12
and p12, p1, p11, LSR #48 // x12 = _cmd & mask
#endif // CONFIG_USE_PREOPT_CACHES
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
ldr p11, [x16, #CACHE] // p11 = mask|buckets
and p10, p11, #~0xf // p10 = buckets
and p11, p11, #0xf // p11 = maskShift
mov p12, #0xffff
lsr p11, p12, p11 // p11 = mask = 0xffff >> p11
and p12, p1, p11 // x12 = _cmd & mask
#else
#error Unsupported cache mask storage for ARM64.
#endif
// p11 cache -> p10 = buckets
// p1(_cmd) & mask = index -> p12
// #define PTRSHIFT 3
// p12(index)左移(PTRSHIFT+1),即左移4位 后给p12
//add p13, p10, p12 : buckets + 左移4位的(p12) 后 给到 p13
add p13, p10, p12, LSL #(1+PTRSHIFT)
// p13 = buckets + ((_cmd & mask) << (1+PTRSHIFT))
// do {
//将*bucket--里面的东西分别存在p17和p9中
//(*bucket--)是拿到bucket里面的东西 imp(p17) sel(p9)
1: ldp p17, p9, [x13], #-BUCKET_SIZE // {imp, sel} = *bucket--
//查到的sel和传入的sel进行比较是否相同,如果相同,就会走到 2 里面,不相同就会走到 3f 里面,其实就是循环查找
cmp p9, p1 // if (sel != _cmd) {
b.ne 3f // scan more
// } else {
//缓存命中 Mode就是传过来的NORMAL
2: CacheHit \Mode // hit: call or return imp
// }
//p9 为空
3: cbz p9, \MissLabelDynamic // if (sel == 0) goto Miss;
//p13 和 p10(buckets首地址)对比 ,如果p13大于等于p10,就回到1,再次*bucket--
cmp p13, p10 // } while (bucket >= buckets)
b.hs 1b
// wrap-around:
// p10 = first bucket
// p11 = mask (and maybe other bits on LP64)
// p12 = _cmd & mask
//
// A full cache can happen with CACHE_ALLOW_FULL_UTILIZATION.
// So stop when we circle back to the first probed bucket
// rather than when hitting the first bucket again.
//
// Note that we might probe the initial bucket twice
// when the first probed slot is the last entry.
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS
add p13, p10, w11, UXTW #(1+PTRSHIFT)
// p13 = buckets + (mask << 1+PTRSHIFT)
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
//定位到最后一个bucket
add p13, p10, p11, LSR #(48 - (1+PTRSHIFT))
// p13 = buckets + (mask << 1+PTRSHIFT)
// see comment about maskZeroBits
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
add p13, p10, p11, LSL #(1+PTRSHIFT)
// p13 = buckets + (mask << 1+PTRSHIFT)
#else
#error Unsupported cache mask storage for ARM64.
#endif
//共享缓存里面也可能存在 给p12
add p12, p10, p12, LSL #(1+PTRSHIFT)
// p12 = first probed bucket
// do {
4: ldp p17, p9, [x13], #-BUCKET_SIZE // {imp, sel} = *bucket--
cmp p9, p1 // if (sel == _cmd)
b.eq 2b // goto hit
cmp p9, #0 // } while (sel != 0 &&
ccmp p13, p12, #0, ne // bucket > first_probed)
b.hi 4b
// CacheHit: x17 = cached IMP, x10 = address of buckets, x1 = SEL, x16 = isa
.macro CacheHit
.if $0 == NORMAL
如果 $0 等于 NORMAL就会调用TailCallCachedImp
TailCallCachedImp x17, x10, x1, x16 // authenticate and call imp
.elseif $0 == GETIMP
mov p0, p17
cbz p0, 9f // don't ptrauth a nil imp
AuthAndResignAsIMP x0, x10, x1, x16 // authenticate imp and re-sign as IMP
9: ret // return IMP
.elseif $0 == LOOKUP
// No nil check for ptrauth: the caller would crash anyway when they
// jump to a nil IMP. We don't care if that jump also fails ptrauth.
AuthAndResignAsIMP x17, x10, x1, x16 // authenticate imp and re-sign as IMP
cmp x16, x15
cinc x16, x16, ne // x16 += 1 when x15 != x16 (for instrumentation ; fallback to the parent class)
ret // return imp via x17
.else
.abort oops
.endif
.macro TailCallCachedImp
// $0 = cached imp, $1 = address of cached imp, $2 = SEL, $3 = isa
// x17 = cached IMP, x10 = address of buckets, x1 = SEL, x16 = isa
// eor 逻辑异或(^)
// $0^$3 = x17 ^ x16 = IMP ^ class 结果再给到$0。IMP编码
eor $0, $0, $3
//调用IMP
br $0
CacheLookup
的逻辑大概如下:
- 将
isa
首地址平移16
个字节大小,得到cache_t
,在objc_class
结构体中,cache
前面还有isa
和superclass
各占8
个字节。 - 将
cache_t
的首地址与上0x0000fffffffffffe
得到buckets
- 将
cache_t
与0
比较,如果不为0
就执行LLookupPreopt\Function
,如果为为0
,执行第4
步 - 将
_objc_msgSend
传入的第二个参数SEL
与上mask
(cache
右移48
位得到mask
)得到bucket
的下标index
buckets
首地址+
偏移量(下标index
左移(PTRSHIFT
+1)位,PTRSHIFT
=3
)取出bucket
do-while
循环,当bucket
地址小于buckets
首地址时循环结束。循环下面操作:
-
6.1 每次按
BUCKET_SIZE
大小将地址减小 -
6.2 取出
bucket
中的SEL
与objc_msgSend
传入的SEL
进行比较是否相同 -
6.3 如果相同,就会缓存命中,执行
CacheHit
,IMP = IMP^ISA
得到IMP
并调用 -
6.4 如果不相同就判断在
bucket
取出的SEL
是否为空,为空就执行MissLabelDynamic
,否则就将bucket
地址每次按BUCKET_SIZE
大小减小,向前查找。 -
6.5 如果循环结束还没找到就执行
__objc_msgSend_uncached
objc_msgSend及CacheLookup流程图
objc_msgSend及CacheLookup流程图总结
objc_msgSend
的流程就是通过se
l拿到imp
,并调用imp
。流程如下:
- 判断
recevier
是否存在 - 通过
recevier
的isa
得到class
class
内存平移拿到cache
,其中包含了bucket
和mask
cache
首地址与上0x0000fffffffffffe
得到buckets
cache
首地址右移48
位得到mask
。需要mask
是因为insert
哈希函数是这样计算的:(mask_t)(value & mask)
- 获得第一次查找的
index
,index = SEL&mask
。 bucket+index
整个缓存里面的第几个bucket
- 取出
bucket
中的sel
sel
与objc_msgSend
传入的sel
进行比较,相等就命中缓存,调用IMP
- 不相等就让
bucket--
,回到第7
步开始循环 - 如果循环结束还没找到就执行
__objc_msgSend_uncached
补充
如图,第一个红框
value
是0
,当调用一个方法时[p saySomething]
(第二个红框),value
是7
,按照之前对Cache_t
的探索,初始容量是4
,当容量大于当前容积的3/4
,就会进行扩容。那么在调用saySomething
时,还有哪些方法占着容量呢?
在 cache_t::insert
方法中 unsigned oldCapacity = capacity(), capacity = oldCapacity;
后面插入以下代码:
if (sel == @selector(saySomething)) {
bucket_t *kc_b = buckets();
for (unsigned i = 0; i<oldCapacity; i++) {
SEL kc_sel = kc_b[i].sel();
IMP kc_imp = kc_b[i].imp(kc_b,nil);
printf("%p - %p - %p\n",kc_sel,kc_imp,&kc_b[i]);
}
printf("isConstantEmptyCahe %p - %u - %u -%u\n",kc_b,capacity,newOccupied,oldCapacity);
}
插入这段代码的目的是在调用saySomething
方法的时候,打印出当前占用容量的方法。lldb
调试结果如下:
前两个分别是
respondsToSelector:
和class
,第三个是空的。第四个imp
地址和第一个的bucket
地址是同一个地址都是0x10144d050
。在allocateBuckets
函数中,
意思是会在列表末端添加一个额外的
bucket
,sel
为1
,并指向第一个bucket
。
回到之前的问题,为什么value
是7
?
因为当前有四个方法,大于当前容积(默认4)的3/4
,所以扩容了。