上篇objc_msgSend
彙編分析獲得了class
,接着開始查找緩存的分析ios
//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
的邏輯大概以下:c++
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
進行比較是否相同markdown
6.3 若是相同,就會緩存命中,執行CacheHit
,IMP = IMP^ISA
獲得IMP
並調用app
6.4 若是不相同就判斷在bucket
取出的SEL
是否爲空,爲空就執行MissLabelDynamic
,不然就將bucket
地址每次按BUCKET_SIZE
大小減少,向前查找。iphone
6.5 若是循環結束還沒找到就執行__objc_msgSend_uncached
函數
objc_msgSend
的流程就是經過se
l拿到imp
,並調用imp
。流程以下:oop
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
時,還有哪些方法佔着容量呢?post
在 cache_t::insert
方法中 unsigned oldCapacity = capacity(), capacity = oldCapacity;
後面插入如下代碼:ui
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
,因此擴容了。