第八节课 消息流程分析之快速查找(下)
上篇文章我们通过源码查看了方法底层调用的逻辑,但是只分析到了objc_msgSend
的主体逻辑,并没有深入了解,那么这篇文章我们就继续深入。
深入前,小小的回顾还是有必要的
主要的流程:
1.判断当前接收者是否存在cmp p0, #0
2.判断是不是SUPPORT_TAGGED_POINTERS
类型。如果是,执行b.le LNilOrTagged
,然后在里面执行 b.eq LReturnZero
。如果不是SUPPORT_TAGGED_POINTERS
类型,直接b.eq LReturnZero
,此次objc_msgSend无效
,结束本次消息发送。
3.如果p0
存在,则将x0
存入到p13
,x0
是receiver
,即类,即类的首地址,即isa
,也就是说p13=isa
。通过isa
拿到class
GetClassFromIsa_p16 p13, 1, x0
4.查看GetClassFromIsa_p16
源码,最终获取到p16=class
5.CacheLookup NORMAL
这篇文章我们就主要来分析这个CacheLookup NORMAL
CacheLookup 缓存查找汇编源码
.macro CacheLookup
//
// Restart protocol:
//
// As soon as we're past the LLookupStart$1 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$1,
// then our PC will be reset to LLookupRecover$1 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
//
mov x15, x16 // stash the original isa
LLookupStart\Function:
// p1 = SEL, p16 = isa
#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
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16 //真机架构
ldr p11, [x16, #CACHE] //x16进行平移#CACHE大小,存储到p11。CACHE全局搜索赋值,是2 * __SIZEOF_POINTER__也就是2倍的指针大小,也就是16.平移后的位置就相当于cache_t的位置。p11 = cache_t
// p11 = mask|buckets
#if CONFIG_USE_PREOPT_CACHES
#if __has_feature(ptrauth_calls)
tbnz p11, #0, LLookupPreopt\Function
and p10, p11, #0x0000ffffffffffff // p10 = buckets
#else
and p10, p11, #0x0000fffffffffffe // p10 = buckets
tbnz p11, #0, LLookupPreopt\Function //不为0就跳转LLookupPreopt
#endif
eor p12, p1, p1, LSR #7
and p12, p12, p11, LSR #48 //cache_t(_bucketsAndMaybeMask)右移48位,即清空了buckets,可以得到mask,最后将p12 & mask,得到了第一次查找bucket_t的index,即first_probed
// x12 = (_cmd ^ (_cmd >> 7)) & mask
#else
// p11 cache -> p10 = buckets
// p11, LSR #48 -> mask
// p1(_cmd) & mask = index -> p12
and p10, p11, #0x0000ffffffffffff // p10 = buckets
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
// objc - 源码调试 + 汇编
// p11 cache -> p10 = buckets
// p1(_cmd) & mask = index -> p12
// (_cmd & mask) << 4 -> int 1 2 3 4 5 地址->int
// buckets + 代表内存平移 (1 2 3 4)
// b[i] -> b + I
// p13 当前查找bucket
add p13, p10, p12, LSL #(1+PTRSHIFT)
// p13 = buckets + ((_cmd & mask) << (1+PTRSHIFT))
// do {
// *bucket-- p17, p9
// bucket 里面的东西 imp (p17) sel (p9)
// 查到的 sel (p9) 和我们 say1
1: ldp p17, p9, [x13], #-BUCKET_SIZE // {imp, sel} = *bucket--
cmp p9, p1 // if (sel != _cmd) {
b.ne 3f // scan more
// } else {
2: CacheHit \Mode // hit: call or return imp
// }
3: cbz p9, \MissLabelDynamic // if (sel == 0) goto Miss;
cmp p13, p10 // } while (bucket >= buckets)
b.hs 1b
主要分为以下几步:
【第一步】通过cache
首地址平移16字节
(因为在objc_class中,cache正好距离首地址16字节,即isa首地址 占8字节,superClass占8字节),获取cache,cache中高16位存mask,低48位存buckets,即p11 = cache
【第二步】从cache中
分别取出buckets和mask
,并由mask根据哈希算法计算出哈希下标
通过cache和掩码
(即0x0000fffffffffffe)的 & 运算
,将高16位mask抹零
,得到buckets指针地址,即p10 = buckets
将cache右移48位
,得到mask,即p11 = mask
将objc_msgSend
的参数p1
(即第二个参数_cmd)& msak
,通过哈希算法
,得到需要查找存储sel-imp的bucket下标index
,即p12 = index = _cmd & mask
,为什么通过这种方式呢?因为在存储sel-imp时
,也是通过同样哈希算法计算哈希下标进行存储
,所以读取也需要通过同样的方式读取。
总结:
文字版流程
- 首先判断
recevier
是否存在 recevier -> isa -> class(p16)
-
class
通过内存平移
->cache(bucket mask)
-
bucket掩码
->bucket
-
mask掩码
->mask
- insert 哈希函数
(mask_t)(value & mask)
- 得到第一次查找的
index
- bucket + index
bucket平移
获取整个缓存里面的第几个 bucket{imp sel}
- 拿到
sel
与_cmd
进行比较sel == _cmd -> cacheHit -> imp^isa = imp(br)调用imp
-
sel != _cmd
bucket再次平移
- 死循环 遍历
- 找不到 ->
_objc_msgSend_uncached
图片版流程