IOS底層探索CacheLookup彙編分析

上篇objc_msgSend彙編分析獲得了class,接着開始查找緩存的分析ios

WX20210629-142531@2x.png

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的邏輯大概以下:c++

  1. isa首地址平移16個字節大小,獲得cache_t,在objc_class結構體中,cache前面還有isasuperclass各佔8個字節。
  2. cache_t的首地址與上0x0000fffffffffffe 獲得buckets
  3. cache_t0比較,若是不爲0就執行LLookupPreopt\Function,若是爲爲0,執行第4
  4. _objc_msgSend傳入的第二個參數SEL與上maskcache右移48位獲得mask)獲得bucket的下標index
  5. buckets首地址+偏移量(下標index左移(PTRSHIFT+1)位,PTRSHIFT=3)取出bucket
  6. do-while循環,當bucket地址小於buckets首地址時循環結束。循環下面操做:
  • 6.1 每次按BUCKET_SIZE大小將地址減少緩存

  • 6.2 取出bucket中的SELobjc_msgSend傳入的SEL進行比較是否相同markdown

  • 6.3 若是相同,就會緩存命中,執行CacheHitIMP = IMP^ISA獲得IMP並調用app

  • 6.4 若是不相同就判斷在bucket取出的SEL是否爲空,爲空就執行MissLabelDynamic,不然就將bucket地址每次按BUCKET_SIZE大小減少,向前查找。iphone

  • 6.5 若是循環結束還沒找到就執行__objc_msgSend_uncached函數

objc_msgSend及CacheLookup流程圖

111.png

objc_msgSend及CacheLookup流程圖總結

objc_msgSend的流程就是經過sel拿到imp,並調用imp。流程以下:oop

  1. 判斷recevier是否存在
  2. 經過recevierisa獲得class
  3. class內存平移拿到cache,其中包含了bucketmask
  4. cache首地址與上0x0000fffffffffffe獲得buckets
  5. cache首地址右移48位獲得mask。須要mask是由於insert哈希函數是這樣計算的:(mask_t)(value & mask)
  6. 得到第一次查找的indexindex = SEL&mask
  7. bucket+index整個緩存裏面的第幾個bucket
  8. 取出bucket中的sel
  9. selobjc_msgSend傳入的sel進行比較,相等就命中緩存,調用IMP
  10. 不相等就讓bucket--,回到第7步開始循環
  11. 若是循環結束還沒找到就執行__objc_msgSend_uncached

補充

圖片.png 如圖,第一個紅框value0,當調用一個方法時[p saySomething](第二個紅框),value7,按照以前對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調試結果以下: 圖片.png 前兩個分別是respondsToSelector:class,第三個是空的。第四個imp地址和第一個的bucket地址是同一個地址都是0x10144d050。在allocateBuckets函數中, 圖片.png 意思是會在列表末端添加一個額外的bucketsel1,並指向第一個bucket

回到以前的問題,爲何value7

由於當前有四個方法,大於當前容積(默認4)的3/4,因此擴容了。

相關文章
相關標籤/搜索