在《OC底層探究之類的cache_t分析》中探索了方法是如何存入類中的緩存的!可是方法是什麼時候存入類的緩存的呢?html
接下來咱們就開始探索方法是什麼時候插入緩存的!java
先打開objc源碼
,在調用方法
處打上斷點
:c++
等斷住後,在insert
方法中打上斷點
,斷住後在lldb
中使用bt
查看堆棧
:算法
在堆棧中能夠看到從對象調用方法
到進入到insert方法
的整個流程:編程
_objc_msgSend_uncached -> lookUpImpOrForward -> log_and_fill_cache -> insertapi
這裏能夠看到對象調用方法的時候最早調用的是objc_msgSend
方法!緩存
而objc_msgSend
方法就涉及到了runtime
!markdown
首先咱們要先知道編程語言有靜態和動態之分。架構
所謂靜態語言,就是在程序運行前決定了全部的類型判斷,類的全部成員、方法在編譯階段(即編譯時)就肯定好了內存地址。也就意味着全部類對象只能訪問屬於本身的成員變量和方法,不然編譯器直接報錯。比較常見的靜態的語言如:java,c++,c等等。app
而動態語言,偏偏相反,類型的判斷、類的成員變量、方法的內存地址都是在程序的運行階段(即運行時)才最終肯定,而且還能動態的添加成員變量和方法。也就意味着你調用一個不存在的方法時,編譯也能經過,甚至一個對象它是什麼類型並非表面咱們所看到的那樣,只有運行以後才能決定其真正的類型。相比於靜態語言,動態語言具備較高的靈活性和可訂閱性。而oc,正是一門動態語言。
編譯時,
顧名思義就是正在編譯的時候。那啥叫編譯呢?就是編譯器幫你把源代碼翻譯成機器能識別的代碼
。(固然只是⼀般意義上這麼說,實際上可能只是翻譯成某個中間狀態的語⾔。)那麼編譯時就是簡單的做⼀些翻譯⼯做,⽐如檢查⽼兄你有沒有粗⼼寫錯啥關鍵字了啊、詞法分析、語法分析之類的過程。
就像個⽼師檢查學⽣的做⽂中有沒有錯別字和病句⼀樣
。若是發現啥錯誤編譯器就告訴你。若是你⽤微軟的VS的話,點下build.那就開始編譯,若是下⾯有errors或者warning信息,那都是編譯器檢查出來的。所謂這時的錯誤就叫編譯時錯誤,這個過程當中作的啥類型檢查也就叫編譯時類型檢查,或靜態類型檢查(所謂靜態嘛就是沒把真把代碼放內存中運⾏起來,⽽只是把代碼看成⽂原本掃描下)。
因此有時⼀些⼈說編譯時還分配內存啥的確定是錯誤的說法
。
運⾏時,就是代碼跑起來了,被裝載到內存中去了。
你的代碼保存在磁盤上沒裝⼊內存以前是個死傢伙,只有跑到內存中才變成活的。⽽運⾏時類型檢查就與前⾯講的編譯時類型檢查(或者靜態類型檢查)不⼀樣.不是簡單的掃描代碼。⽽是在內存中作些操做,作些判斷。
關於運行時更多詳細的內容能夠去官方文檔上進行查閱。(Objective-C 運行時編程指南)
一、OC的方法。
二、NSObject的方法。
三、objc動態庫的api。
能夠經過一個圖來表示層級:
建立一個對象,有兩個方法,只實現一個方法,運行一下:
就會發現,編譯
是成功的,可是一運行
就報錯了,這就是編譯時
和運行時
的區別!
接下來咱們經過clang
的還原,看看OC代碼
在底層的實現,找到main
函數:
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
HPerson * p = ((HPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("HPerson"), sel_registerName("alloc"));
((void (*)(id, SEL))(void *)objc_msgSend)((id)p, sel_registerName("saySix"));
((void (*)(id, SEL))(void *)objc_msgSend)((id)p, sel_registerName("sayHello"));
}
return 0;
}
複製代碼
發現編譯後上層的代碼都會獲得一個解釋
!
調用方法的過程就是調用objc_msgSend
函數,即消息發送!
經過底層代碼咱們能夠發現objc_msgSend
函數有2個參數:
一個是(id)objc_getClass("HPerson")
或者(id)p
,即消息的接受者!
一個是sel_registerName("xxx")
即sel
!
如今調用的方法都是沒有帶參數的,若是是帶有參數的呢?
咱們加上參數:
再clang
一下:
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
HPerson * p = ((HPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("HPerson"), sel_registerName("alloc"));
((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)p, sel_registerName("saySomething:"), (NSString *)&__NSConstantStringImpl__var_folders_1h_55lzq4fd39b0mz94wmqthqf860mpgy_T_main_189844_mi_2);
((void (*)(id, SEL))(void *)objc_msgSend)((id)p, sel_registerName("saySix"));
((void (*)(id, SEL))(void *)objc_msgSend)((id)p, sel_registerName("sayHello"));
}
return 0;
}
複製代碼
能夠看到多了一個NSString
參數!
因此能夠得出消息發送方式
:
objc_msgSend (消息接受者, 消息主體(sel + 參數))
那咱們是否能夠直接在代碼中進行調用呢?
在調用以前先在Build Setting
中將Enable Strict Checking of objc_msgSend Calls
改成NO:
意思是對調用objc_msgSend
函數的要求放寬,由咱們來決定參數!
而後咱們嘗試直接在代碼中調用:
發現和咱們正常使用代碼調用方法是如出一轍的!
那麼NSObject
有是怎麼調用的呢?
進入到NSObject.h
中就能夠看到相關方法了:
很明顯performSelector
和方法
有關,咱們來嘗試一下:
和咱們正常調用是同樣的!
先打開彙編
:
而後在方法前打上斷點
,運行:
在objc_msgSend
處打上斷點,斷住後,按住ctrl
點擊step into
:
便可發現objc_msgSend
是來自objc
底層源碼!
打開objc
源碼,搜索objc_msgSend
:
由於是objc_msgSend
的底層是彙編寫的,因此咱們直接看.s
文件!
由於咱們用的最多仍是真機,因此咱們看arm64的:
找到ENTRY _objc_msgSend
:
ENTRY _objc_msgSend
UNWIND _objc_msgSend, NoFrame
cmp p0, #0 // nil check and tagged pointer check
#if SUPPORT_TAGGED_POINTERS
b.le LNilOrTagged // (MSB tagged pointer looks negative)
#else
b.eq LReturnZero
#endif
ldr p13, [x0] // p13 = isa
GetClassFromIsa_p16 p13, 1, x0 // p16 = class
LGetIsaDone:
// calls imp or objc_msgSend_uncached
CacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncached
複製代碼
咱們會發現_objc_msgSend
是用彙編
寫的,而咱們以前的源碼都是用c
或者c++
寫的,這是爲何呢?
由於彙編快
,並且具備動態化
!
b 指令
bl
跳轉到標號出執行b.le
判斷上面cmp的值是小於等於 執行標號,不然直接往下走b.ge
大於等於 執行地址 不然往下b.lt
判斷上面camp的值是 小於 執行後面的地址中的方法 不然直接往下走b.gt
大於 執行地址 不然往下b.eq
等於 執行地址 不然往下b.hi
比較結果是無符號大於,執行地址中的方法,不然不跳轉b.hs
指令是判斷是否無符號小於b.ls
指令是判斷是否無符號大於b.lo
指令是判斷是否無符號大於等於ret 返回
mov x0,#0x10 -> x0 = 0x10
str w10 ,[sp]
將w10寄存器的值存到 sp棧空間內存stp x0,x1,[sp.#0x10]*
: x0、x1 的值存入 sp + 0x10orr x0,wzr,#0x1
: x0 = wzr | 0x1stur w10 ,[sp]
將w10寄存器的值存到 sp棧空間內存ldr w10 ,[sp]
w10 = sp棧內存中的值ldp x0,x1,[sp]
x0、x1 = sp棧內存中的值
adrp
經過基地址 + 偏移 得到一個字符串(全局變量)
cbz
比較,爲零則跳轉;cbnz
: 比較,爲非零則跳轉。cmp: 比較功能
例如 : cmp OPR1 , OPR2. = (OPR1)-(OPR2)
cmp p0, #0 // nil check and tagged pointer check
複製代碼
搜索p0
:
#if __LP64__
// true arm64
#define SUPPORT_TAGGED_POINTERS 1
#define PTR .quad
#define PTRSIZE 8
#define PTRSHIFT 3 // 1<<PTRSHIFT == PTRSIZE
// "p" registers are pointer-sized
#define UXTP UXTX
#define p0 x0
#define p1 x1
#define p2 x2
#define p3 x3
#define p4 x4
#define p5 x5
#define p6 x6
#define p7 x7
#define p8 x8
#define p9 x9
#define p10 x10
#define p11 x11
#define p12 x12
#define p13 x13
#define p14 x14
#define p15 x15
#define p16 x16
#define p17 x17
// true arm64
#else
// arm64_32
#define SUPPORT_TAGGED_POINTERS 0
#define PTR .long
#define PTRSIZE 4
#define PTRSHIFT 2 // 1<<PTRSHIFT == PTRSIZE
// "p" registers are pointer-sized
#define UXTP UXTW
#define p0 w0
#define p1 w1
#define p2 w2
#define p3 w3
#define p4 w4
#define p5 w5
#define p6 w6
#define p7 w7
#define p8 w8
#define p9 w9
#define p10 w10
#define p11 w11
#define p12 w12
#define p13 w13
#define p14 w14
#define p15 w15
#define p16 w16
#define p17 w17
// arm64_32
#endif
複製代碼
能夠發現p0
,是指寄存器x0
!即咱們傳入的第一個參數p
。
這條指令的意思就讓p0
和0
進行對比,判斷p0
是否爲空
!
即判斷消息接收者
是否存在,若是不存在則:
#if SUPPORT_TAGGED_POINTERS
b.le LNilOrTagged // (MSB tagged pointer looks negative)
#else
b.eq LReturnZero
#endif
複製代碼
當SUPPORT_TAGGED_POINTERS
爲1
時,進入LNilOrTagged
:
#if SUPPORT_TAGGED_POINTERS
LNilOrTagged:
b.eq LReturnZero // nil check
GetTaggedClass
b LGetIsaDone
// SUPPORT_TAGGED_POINTERS
#endif
複製代碼
和SUPPORT_TAGGED_POINTERS
爲0
時相似,最後都是執行LReturnZero
:
LReturnZero:
// x0 is already zero
mov x1, #0
movi d0, #0
movi d1, #0
movi d2, #0
movi d3, #0
ret
END_ENTRY _objc_msgSend
複製代碼
即把寄存器清空,而後結束_objc_msgSend
!
正常狀況固然是繼續往下走:
ldr p13, [x0] // p13 = isa
複製代碼
將x0
賦值給p13
,x0
便是消息接受者
,即傳入的第一個參數p
。
註釋代表是將isa
賦值給p13
,爲何是isa
呢?
由於isa
即p
的首地址
!
繼續:
GetClassFromIsa_p16 p13, 1, x0 // p16 = class
複製代碼
看註釋代表是將class
賦值給了p16
!
開始探索GetClassFromIsa_p16
!
調用GetClassFromIsa_p16
,並將p13
,1
,x0
傳入。
進入GetClassFromIsa_p16
:
/******************************************************************** * GetClassFromIsa_p16 src, needs_auth, auth_address * src is a raw isa field. Sets p16 to the corresponding class pointer. * The raw isa might be an indexed isa to be decoded, or a * packed isa that needs to be masked. * * On exit: * src is unchanged * p16 is a class pointer * x10 is clobbered ********************************************************************/
.macro GetClassFromIsa_p16 src, needs_auth, auth_address /* note: auth_address is not required if !needs_auth */
#if SUPPORT_INDEXED_ISA
// Indexed isa
mov p16, \src // optimistically set dst = src
tbz p16, #ISA_INDEX_IS_NPI_BIT, 1f // done if not non-pointer isa
// isa in p16 is indexed
adrp x10, _objc_indexed_classes@PAGE
add x10, x10, _objc_indexed_classes@PAGEOFF
ubfx p16, p16, #ISA_INDEX_SHIFT, #ISA_INDEX_BITS // extract index
ldr p16, [x10, p16, UXTP #PTRSHIFT] // load class from array
1:
#elif __LP64__
.if \needs_auth == 0 // _cache_getImp takes an authed class already
mov p16, \src
.else
// 64-bit packed isa
ExtractISA p16, \src, \auth_address
.endif
#else
// 32-bit raw isa
mov p16, \src
#endif
.endmacro
複製代碼
.macro
表示這是一個宏定義!
先看SUPPORT_INDEXED_ISA
:
// Define SUPPORT_INDEXED_ISA=1 on platforms that store the class in the isa
// field as an index into a class table.
// Note, keep this in sync with any .s files which also define it.
// Be sure to edit objc-abi.h as well.
#if __ARM_ARCH_7K__ >= 2 || (__arm64__ && !__LP64__)
# define SUPPORT_INDEXED_ISA 1
#else
# define SUPPORT_INDEXED_ISA 0
#endif
複製代碼
如今基本都是64
位,因此咱們主要看SUPPORT_INDEXED_ISA
爲0
的狀況,以及爲__LP64__
的狀況:
.if \needs_auth == 0 // _cache_getImp takes an authed class already
mov p16, \src
.else
// 64-bit packed isa
ExtractISA p16, \src, \auth_address
.endif
複製代碼
needs_auth
爲第二個參數,即1!
因此走的是ExtractISA
,傳入p16
,p13(isa)
,x0
:
.macro ExtractISA and $0, $1, #ISA_MASK.endmacro
複製代碼
這裏是讓$1
和#ISA_MASK
相加,而後賦值
給了$0
!
這裏和咱們以前看的源碼很像!即isa&ISA_MASK
,獲得class
!
因此這一步就將class
賦值給了p16
!
爲何要把class
取出來呢?由於cache
是在class
裏面,取出class
後就要準備插入緩存了!
而後結束GetClassFromIsa_p16
!
獲取了class
後:
LGetIsaDone:
// calls imp or objc_msgSend_uncached
CacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncached
複製代碼
接着就進入到了CacheLookup
,即查找緩存:
/******************************************************************** * * CacheLookup NORMAL|GETIMP|LOOKUP <function> MissLabelDynamic MissLabelConstant * * MissLabelConstant is only used for the GETIMP variant. * * Locate the implementation for a selector in a class method cache. * * When this is used in a function that doesn't hold the runtime lock, * this represents the critical section that may access dead memory. * If the kernel causes one of these functions to go down the recovery * path, we pretend the lookup failed by jumping the JumpMiss branch. * * Takes: * x1 = selector * x16 = class to be searched * * Kills: * x9,x10,x11,x12,x13,x15,x17 * * Untouched: * x14 * * On exit: (found) calls or returns IMP * with x16 = class, x17 = IMP * In LOOKUP mode, the two low bits are set to 0x3 * if we hit a constant cache (used in objc_trace) * (not found) jumps to LCacheMiss * with x15 = class * For constant caches in LOOKUP mode, the low bit * of x16 is set to 0x1 to indicate we had to fallback. * In addition, when LCacheMiss is __objc_msgSend_uncached or * __objc_msgLookup_uncached, 0x2 will be set in x16 * to remember we took the slowpath. * So the two low bits of x16 on exit mean: * 0: dynamic hit * 1: fallback to the parent class, when there is a preoptimized cache * 2: slowpath * 3: preoptimized cache hit * ********************************************************************/
#define NORMAL 0
#define GETIMP 1
#define LOOKUP 2
.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
//
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] // 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
#endif
eor p12, p1, p1, LSR #7
and p12, p12, p11, LSR #48 // x12 = (_cmd ^ (_cmd >> 7)) & mask
#else
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
add p13, p10, p12, LSL #(1+PTRSHIFT)
// p13 = buckets + ((_cmd & mask) << (1+PTRSHIFT))
// do {
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
// 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
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
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
LLookupEnd\Function:
LLookupRecover\Function:
b \MissLabelDynamic
#if CONFIG_USE_PREOPT_CACHES
#if CACHE_MASK_STORAGE != CACHE_MASK_STORAGE_HIGH_16
#error config unsupported
#endif
LLookupPreopt\Function:
#if __has_feature(ptrauth_calls)
and p10, p11, #0x007ffffffffffffe // p10 = buckets
autdb x10, x16 // auth as early as possible
#endif
// x12 = (_cmd - first_shared_cache_sel)
adrp x9, _MagicSelRef@PAGE
ldr p9, [x9, _MagicSelRef@PAGEOFF]
sub p12, p1, p9
// w9 = ((_cmd - first_shared_cache_sel) >> hash_shift & hash_mask)
#if __has_feature(ptrauth_calls)
// bits 63..60 of x11 are the number of bits in hash_mask
// bits 59..55 of x11 is hash_shift
lsr x17, x11, #55 // w17 = (hash_shift, ...)
lsr w9, w12, w17 // >>= shift
lsr x17, x11, #60 // w17 = mask_bits
mov x11, #0x7fff
lsr x11, x11, x17 // p11 = mask (0x7fff >> mask_bits)
and x9, x9, x11 // &= mask
#else
// bits 63..53 of x11 is hash_mask
// bits 52..48 of x11 is hash_shift
lsr x17, x11, #48 // w17 = (hash_shift, hash_mask)
lsr w9, w12, w17 // >>= shift
and x9, x9, x11, LSR #53 // &= mask
#endif
ldr x17, [x10, x9, LSL #3] // x17 == sel_offs | (imp_offs << 32)
cmp x12, w17, uxtw
.if \Mode == GETIMP
b.ne \MissLabelConstant // cache miss
sub x0, x16, x17, LSR #32 // imp = isa - imp_offs
SignAsImp x0
ret
.else
b.ne 5f // cache miss
sub x17, x16, x17, LSR #32 // imp = isa - imp_offs
.if \Mode == NORMAL
br x17
.elseif \Mode == LOOKUP
orr x16, x16, #3 // for instrumentation, note that we hit a constant cache
SignAsImp x17
ret
.else
.abort unhandled mode \Mode
.endif
5: ldursw x9, [x10, #-8] // offset -8 is the fallback offset
add x16, x16, x9 // compute the fallback isa
b LLookupStart\Function // lookup again with a new isa
.endif
#endif // CONFIG_USE_PREOPT_CACHES
.endmacro
複製代碼
首先看傳入的參數:NORMAL
, _objc_msgSend
, __objc_msgSend_uncached
,一共3
個參數。
分別對應Mode
, Function
, MissLabelDynamic
, MissLabelConstant
!
可是這個函數須要傳入4
個參數,說明最後一個爲默認值
!
而後跟着繼續往下走:
mov x15, x16 // stash the original isa
複製代碼
這裏是將x16
即class
賦值給了x15
。
繼續:
LLookupStart\Function: // p1 = SEL, p16 = isa
複製代碼
Function
爲傳入的_objc_msgSend
,即開始_objc_msgSend
!
接着就來到了一個判斷
:
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS
//...
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
//...
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
//...
#else
#error Unsupported cache mask storage for ARM64.
#endif
複製代碼
咱們先看看CACHE_MASK_STORAGE
:
#if defined(__arm64__) && __LP64__
#if TARGET_OS_OSX || TARGET_OS_SIMULATOR
#define CACHE_MASK_STORAGE CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS
#else
#define CACHE_MASK_STORAGE CACHE_MASK_STORAGE_HIGH_16 //真機64位
#endif
#elif defined(__arm64__) && !__LP64__
#define CACHE_MASK_STORAGE CACHE_MASK_STORAGE_LOW_4
#else
#define CACHE_MASK_STORAGE CACHE_MASK_STORAGE_OUTLINED
#endif
複製代碼
由於咱們主要看真機64位
的模式,因此只須要看CACHE_MASK_STORAGE_HIGH_16
便可:
ldr p11, [x16, #CACHE] // 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
#endif
eor p12, p1, p1, LSR #7
and p12, p12, p11, LSR #48 // x12 = (_cmd ^ (_cmd >> 7)) & mask
#else
and p10, p11, #0x0000ffffffffffff // p10 = buckets
and p12, p1, p11, LSR #48 // x12 = _cmd & mask
#endif // CONFIG_USE_PREOPT_CACHES
複製代碼
先看ldr
,即存儲值
,這裏是先把x16
加上#CACHE
,而後在賦值
給p11
!
找一下#define CACHE
:
#define CACHE (2 * __SIZEOF_POINTER__)
複製代碼
__SIZEOF_POINTER__
是指針的大小,即CACHE
爲16
!
因此是x16
平移16字節
,即class
平移16字節
獲得cache
!再賦值給p11
,即p11
就是cache
的首地址(即_bucketsAndMaybeMask
)!
接着看CONFIG_USE_PREOPT_CACHES
:
#if defined(__arm64__) && TARGET_OS_IOS && !TARGET_OS_SIMULATOR && !TARGET_OS_MACCATALYST
#define CONFIG_USE_PREOPT_CACHES 1
#else
#define CONFIG_USE_PREOPT_CACHES 0
#endif
複製代碼
真機時爲1
!即只用看這一段:
#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
複製代碼
__has_feature
:此函數的功能是判斷編譯器是否支持某個功能。
ptrauth_calls
:指針身份驗證,針對 arm64e
架構;使用 Apple A12
或更高版本 A
系列處理器的設備(如 iPhone XS
、iPhone XS Max
和 iPhone XR
或更新的設備)支持 arm64e
架構。
咱們看大多數狀況,即A12
如下的真機
,即else
部分!
先是把p11&0x0000fffffffffffe
再賦值給了p10
!那麼p10
是什麼呢?
回顧獲取buckets
的方法:
struct bucket_t *cache_t::buckets() const {
uintptr_t addr = _bucketsAndMaybeMask.load(memory_order_relaxed);
return (bucket_t *)(addr & bucketsMask);
}
複製代碼
這裏p11
是cache
的首地址,即_bucketsAndMaybeMask
,而0x0000fffffffffffe
爲掩碼,因此p10
爲buckets
!
拿到buckets
後:
tbnz p11, #0, LLookupPreopt\Function
複製代碼
tbnz
:第0位不爲0則發生跳轉。
通常狀況buckets
都是爲0的。
繼續往下走:
eor p12, p1, p1, LSR #7
and p12, p12, p11, LSR #48 // x12 = (_cmd ^ (_cmd >> 7)) & mask
複製代碼
LSR
:按位右移。
這裏和以前insert
方法的獲取hash
是同樣的:
static inline mask_t cache_hash(SEL sel, mask_t mask) {
uintptr_t value = (uintptr_t)sel;
#if CONFIG_USE_PREOPT_CACHES
value ^= value >> 7;
#endif
return (mask_t)(value & mask);
}
複製代碼
因此這裏是從新hash
,獲取下標
,即p12
爲開始值(begin)
!
拿到哈希下標後:
add p13, p10, p12, LSL #(1+PTRSHIFT)
// p13 = buckets + ((_cmd & mask) << (1+PTRSHIFT))
複製代碼
LSL
:按位左移。
而後找PTRSHIFT
:
#if __LP64__
#define PTRSHIFT 3 // 1<<PTRSHIFT == PTRSIZE
#else
#define PTRSHIFT 2 // 1<<PTRSHIFT == PTRSIZE
#endif
複製代碼
因此PTRSHIFT
爲3
!
這裏p12
爲hash
下標,左移4
位則p12的值
乘以16
即爲內存大小!
因此這裏是p10
這個buckets
進行內存平移取bucket
!
因此p13
是當前查找的bucket
!
接着:
// do {
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
複製代碼
ldp x0,x1,[sp]
:x0、x1 = sp棧內存中的值。
cbz
:比較,爲零則跳轉。
分析:
1:將當前bucket
的imp
和sel
分別賦值給p17
和p9
,而後x13-16
,即x13
爲前一個bucket
,接着用p9(sel)
和咱們傳進來的方法
進行比較,若是不同則跳到3
,若是同樣則跳到2
。
二、找到了咱們傳進來的方法,即緩存命中CacheHit
。
三、若是p9(sel)
爲空,則進入MissLabelDynamic
即CacheLookup
函數傳入的第三個參數__objc_msgSend_uncached
,若是不爲空,判斷當前x13
這個bucket
地址是否大於等於p10
這個buckets
首地址,大於等於則跳轉到1
,不然接着往下走!
這3步
就是一個do...while
循環!
爲何p9(sel)爲空就miss呢?
一、首先取緩存和存緩存的hash算法
是同樣的。
二、真機狀況下,存緩存的時候若是hash衝突
了,則:
static inline mask_t cache_next(mask_t i, mask_t mask) {
return i ? i-1 : mask;
}
複製代碼
說明真機是按位存儲
緩存的,因此發現爲空說明緩存已經找完了
,並且沒有找到,則miss!
同理,當bucket
的地址小於buckets
的地址時,也說明緩存已找完了
,即跳出
循環!
當找到了咱們傳入的方法後,就會進入到緩存命中CacheHit
:
// CacheHit: x17 = cached IMP, x10 = address of buckets, x1 = SEL, x16 = isa
.macro CacheHit
.if $0 == NORMAL
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
.endmacro
複製代碼
$0
爲CacheLookup
函數傳入的第一個值,即爲NORMAL
!
因此只用看:
TailCallCachedImp x17, x10, x1, x16 // authenticate and call imp
複製代碼
接着進入TailCallCachedImp
函數:
#if __has_feature(ptrauth_calls)
// JOP
.macro TailCallCachedImp
// $0 = cached imp, $1 = address of cached imp, $2 = SEL, $3 = isa
eor $1, $1, $2 // mix SEL into ptrauth modifier
eor $1, $1, $3 // mix isa into ptrauth modifier
brab $0, $1
.endmacro
#else
// not JOP
.macro TailCallCachedImp
// $0 = cached imp, $1 = address of cached imp, $2 = SEL, $3 = isa
eor $0, $0, $3
br $0
.endmacro
#endif
複製代碼
咱們看A12
如下的狀況:
.macro TailCallCachedImp
// $0 = cached imp, $1 = address of cached imp, $2 = SEL, $3 = isa
eor $0, $0, $3
br $0
.endmacro
複製代碼
eor
:按位異或。
咱們傳入了x17(imp
), x10(buckets)
, x1(傳入的sel)
, x16(isa)
!
而後把$0(imp)
和$3(isa即類)
進行異或
,再賦值給$0
。
這裏爲何要異或呢?
在咱們插入緩存的時候,進行了編碼
:
// Sign newImp, with &_imp, newSel, and cls as modifiers.
uintptr_t encodeImp(UNUSED_WITHOUT_PTRAUTH bucket_t *base, IMP newImp, UNUSED_WITHOUT_PTRAUTH SEL newSel, Class cls) const {
if (!newImp) return 0;
#if CACHE_IMP_ENCODING == CACHE_IMP_ENCODING_PTRAUTH
return (uintptr_t)
ptrauth_auth_and_resign(newImp,
ptrauth_key_function_pointer, 0,
ptrauth_key_process_dependent_code,
modifierForSEL(base, newSel, cls));
#elif CACHE_IMP_ENCODING == CACHE_IMP_ENCODING_ISA_XOR
return (uintptr_t)newImp ^ (uintptr_t)cls;
#elif CACHE_IMP_ENCODING == CACHE_IMP_ENCODING_NONE
return (uintptr_t)newImp;
#else
#error Unknown method cache IMP encoding.
#endif
}
複製代碼
因此這裏是解碼
得到imp
!
最後跳轉imp
!
到這裏就是objc_msgSend
經過sel
查找imp
的過程!
若是p13(bucket)
比p10(buckets)
小,則跳出循環,繼續往下走:
// 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
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
複製代碼
只用看CACHE_MASK_STORAGE_HIGH_16
:
add p13, p10, p11, LSR #(48 - (1+PTRSHIFT))
// p13 = buckets + (mask << 1+PTRSHIFT)
// see comment about maskZeroBits
複製代碼
這裏mask
是buckets
開闢的內存-1
,因此這裏是把buckets
右移mask
個內存!
因此p13
爲buckets
的最後一個bucket
!
接着:
add p12, p10, p12, LSL #(1+PTRSHIFT)
// p12 = first probed bucket
複製代碼
這裏是p12(begin)
左移4
位,即經過hash
獲得的begin * 16
,而後再把p10(buckets)
右移p12
大小的內存。
因此p12
爲以前hash
獲得的begin
處的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
複製代碼
這裏上面的循環相似:
一、先賦值imp
、sel
給p17
、p9
,而後bucket--
。
二、比較p9(sel)
和傳入的sel
比較,相同則進入緩存命中CacheHit
。
三、比較p9(sel)
是否不爲0
,並且bukcet
是否大於p12(begin處的bucket)
。
四、知足則跳回4
(循環的開始處)。
若是p9(sel)
爲0
,或者bukcet
小於p12(begin處的bucket)
,則繼續:
LLookupEnd\Function:
LLookupRecover\Function:
b \MissLabelDynamic
複製代碼
這裏就是沒有找到緩存
,進入MissLabelDynamic(__objc_msgSend_uncached)
。