oc是动态语言,sel会在运行时查找imp的内存地址
相对于静态语言,在编译期间已经确定了imp的内存地址。
objc_msgSend arm64源码(objc 818),818源码的缓存在781的基础上多了个满状态缓存,缓存会在size不够时进行扩容,而818会判断是否支持满状态缓存从而暂不进行扩容。
缓存中存储的是方法。
ENTRY _objc_msgSend
UNWIND _objc_msgSend, NoFrame
cmp p0, #0 // nil check and tagged pointer check ,cmp比较p0是为nil
#if SUPPORT_TAGGED_POINTERS //是否支持taggedpointer类型,
b.le LNilOrTagged // (MSB tagged pointer looks negative) 判断是否为nil或者taggedpointer类型
#else
b.eq LReturnZero
#endif
ldr p13, [x0] // p13 = isa, 将存储器地址为x0的字数据读入寄存器p13。
GetClassFromIsa_p16 p13, 1, x0 // p16 = class
LGetIsaDone:
// calls imp or objc_msgSend_uncached
CacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncached
#if SUPPORT_TAGGED_POINTERS
LNilOrTagged:
b.eq LReturnZero // nil check
GetTaggedClass
b LGetIsaDone
// SUPPORT_TAGGED_POINTERS
#endif
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
方法的第一个参数p0是否为空
- 如果支持
tagged pointer
,跳转至LNilOrTagged
,判断是否为nil或者tagged类型,不是就到2 - 如果不支持tagged pointer,跳转至LReturnZero
-
ldr p13, [x0]
,从p0中取出isa存入p13寄存器,
通过GetClassFromIsa_p16
中,arm64架构下通过 isa & ISA_MASK 获取shiftcls位域的类信息,即class
.macro GetClassFromIsa_p16 src, needs_auth, auth_address /* note: auth_address is not required if !needs_auth */
// 64-bit packed isa
ExtractISA p16, \src, \auth_address
.endmacro
.macro ExtractISA
and $0, $1, #ISA_MASK
.endmacro
- 开始从缓存
CacheLookup
查找imp,缓存没有调用__objc_msgSend_uncached
CacheLookup部分源码
.macro CacheLookup Mode, Function, MissLabelDynamic, MissLabelConstant
mov x15, x16 // stash the original isa
LLookupStart\Function:
// p1 = SEL, p16 = isa
//1.将存储器地址为x16+#CACHE的字数据读入寄存器
//移动#CACHE个字节得到cache对象。class的结构是isa,superclass,cache,其中 #CACHE为16,从而得到cache_t的首地址
//而cache_t的首地址是_bucketsAndMaybeMask,
ldr p10, [x16, #CACHE] // p10 = mask|buckets p10,
//2. lsr 右移48位,得到mask,根据setBucketsAndMask的计算方法mask在高16位,低48位为bucket
lsr p11, p10, #48 // p11 = mask //
//3. buckets
and p10, p10, #0xffffffffffff // p10 = buckets
//4. 索引,因为cache在缓存的时候使用cache_hash(sel,mask)
//生成一个hash值存入缓存指定位置,
and w12, w1, w11 // x12 = _cmd & mask
add p13, p10, p12, LSL #(1+PTRSHIFT)
// 5 buckets 首地址+ hash索引值*16,取出该位置的sel和imp,因为bucket中存储的是sel和imp共占有16个字节
// p13 = buckets + ((_cmd & mask) << (1+PTRSHIFT)),(_cmd & mask) 索引
//6.判断是否相等,如果相等就命中,如果不相等则向前查找
//因为在cache_t::insert()方法中的 cache_next(i,mask) == i? i-1 : mask;它是当发生hash碰撞时向前查找有没有空位的。
// do {
1: ldp p17, p9, [x13], #-BUCKET_SIZE // {imp, sel} = *bucket--
cmp p9, p1 // if (sel != _cmd) {
b.ne 3f // scan more
// } else {
//7. 缓存命中就执行 cachehit Nomal
2: CacheHit \Mode // hit: call or return imp
// }
//8. 如果缓存中没有找到,跳转到MissLabelDynamic,
//由于这里调用的是CacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncached
//MissLabelDynamic的会执行__objc_msgSend_uncached
3: cbz p9, \MissLabelDynamic // if (sel == 0) goto Miss;
cmp p13, p10 // } while (bucket >= buckets)
b.hs 1b
//818多了个缓存满状态的查询
add p13, p10, w11, UXTW #(1+PTRSHIFT)
// p13 = buckets + (mask << 1+PTRSHIFT)
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
.....
.endmacro
- p16(isa)移动16个字节获取cache的地址。cache_t的首地址是
_bucketsAndMaybeMask
,并获取mask和buckets - 获取cmd存储在缓存中的索引,因为
cache_t::insert()
插入缓存的时候使用cache_hash(sel,mask){return sel & mask;}
生成一个hash值指定存入缓存的位置,所以读取的时候也要先获取这个索引 - 根据索引从
buckets
中获取相应位置的bucket={imp, sel}
, buckets首地址+ 索引值*16 = 该位置的sel和imp,因为bucket中存储的是sel和imp共占有16个字节。 - 判断
sel == cmd
是否成立,如果相等就命中cachehit
,如果不相等则向前查找*buckets--
,因为在cache_t::insert()
方法中当发生hash碰撞时向前查找有没有空位的cache_next(i,mask) {return i? i-1 : mask;}
-
CacheHit
缓存命中则取缓存中查找imp的地址 - 没有则执行
__objc_msgSend_uncached
,由于objc_msgSend
调用的是CacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncached
,第三个参数MissLabelDynamic
会执行__objc_msgSend_uncached
方法
__objc_msgSend_uncached源码
STATIC_ENTRY __objc_msgSend_uncached
UNWIND __objc_msgSend_uncached, FrameWithNoSaves
// THIS IS NOT A CALLABLE C FUNCTION
// Out-of-band p15 is the class to search
//调用MethodTableLookup方法
MethodTableLookup
TailCallFunctionPointer x17
END_ENTRY __objc_msgSend_uncached
//MethodTableLookup方法
.macro MethodTableLookup
SAVE_REGS MSGSEND
// lookUpImpOrForward(obj, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER)
// receiver and selector already in x0 and x1
mov x2, x16
mov x3, #3
//跳转到_lookUpImpOrForward方法,这是个C方法
bl _lookUpImpOrForward
// IMP in x0
mov x17, x0
RESTORE_REGS MSGSEND
.endmacro
缓存没有找到则会调用_lookUpImpOrForward