寄存器對(duì)應(yīng)須知:
函數(shù)參數(shù)寄存器(%rdi, %rsi, %rdx, %rcx, %r8, %r9)
64位 32位 16位 8位
%rax %eax %ax %al
%rdi %edi %di %dil
%r8 %r8d %r8w %r8b
#define a1 rdi //64位
#define a1d edi //32位
#define a1b dil //8位
#define a2 rsi //64位
#define a2d esi //32位
#define a2b sil //8位
#define a3 rdx //64位
#define a3d edx //32位
#define a4 rcx //64位
#define a4d ecx //32位
#define a5 r8 //64位
#define a5d r8d //32位
#define a6 r9 //64位
#define a6d r9d //32位
id objc_msgSend(id self, SEL _cmd,...)匯編實(shí)現(xiàn)
ENTRY _objc_msgSend
UNWIND _objc_msgSend, NoFrame
NilTest NORMAL (1)receiver為nil則直接ZeroReturn(帶ret指令);否則繼續(xù)往下走。
GetIsaFast NORMAL (2)r10 = self->isa 不為nil則獲取receiver的isa,即所屬類cls的地址
(3) calls IMP on success OR LCacheMiss_objc_msgSend 要么執(zhí)行imp,要么緩存未命中跳至LCacheMiss_objc_msgSend執(zhí)行
CacheLookup NORMAL, CALL, _objc_msgSend
(4)返回前清空%rax和%rdx等返回型寄存器:因?yàn)檎瓦\(yùn)算至多使用這兩個(gè)寄存器存放返回值。清空后函數(shù)就返回了。
// 那為什么要清空呢?因?yàn)開(kāi)objc_msgSend是imp查找、執(zhí)行流,不需要返回值。
NilTestReturnZero NORMAL //ZeroReturn(帶ret指令)
GetIsaSupport NORMAL (5)這條語(yǔ)句似乎不會(huì)執(zhí)行啊。。。
//cache miss: go search the method lists
LCacheMiss_objc_msgSend:
// isa still in r10
jmp __objc_msgSend_uncached //進(jìn)入慢速查找:Method Lists ---> lookupImpOrForward
END_ENTRY _objc_msgSend
其匯編實(shí)現(xiàn)等價(jià)于C語(yǔ)言實(shí)現(xiàn):
id objc_msgSend(id self, SEL _cmd,...){
(1)`NilTest NORMAL`:對(duì)receiver(即self)進(jìn)行非空測(cè)試。
if (!self) return;
(2)`GetIsaFast NORMAL`:receiver非空,獲取其類cls的地址。
Class cls = self->getIsa();
(3)`CacheLookup NORMAL, CALL, _objc_msgSend`:遍歷cls的緩存查找_cmd,如命中則返回其Imp;未命中則開(kāi)啟慢查找。
IMP imp = CacheLookup(cls,_cmd);
if (!imp) {
(4)`LCacheMiss_objc_msgSend` ---> `__objc_msgSend_uncached` ---> `MethodTableLookup NORMAL`--->
`lookUpImpOrForward(self, _cmd, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER);`:緩存未命中,開(kāi)啟慢查找 ---> 方法列表查找
imp = MethodTableLookup(cls,_cmd);
}
jmp *imp; //匯編語(yǔ)句:執(zhí)行imp。
return 0;
}
注意:
(0)匯編“函數(shù)”與C/OC函數(shù)的互調(diào):OC代碼調(diào)用匯編“函數(shù)”時(shí)會(huì)給函數(shù)加上一個(gè)_,如objc_msgSend--->_objc_msgSend,_objc_msgSend_uncached--->__objc_msgSend_uncached,lookupImpOrForward--->_lookupImpOrForward等;反過(guò)來(lái)會(huì)去掉_,只是作以區(qū)分,知道就好。
(1)其中CacheLookup()為表意函數(shù),即假函數(shù);MethodTableLookup()也為表意函數(shù),但MethodTableLookup同時(shí)也為匯編宏;getIsa()為真函數(shù)。
(2)總的來(lái)說(shuō),objc_msgSend()為imp查找、執(zhí)行函數(shù)。它的返回值存放在了%rax/%rdx或%xmm0/%xmm1中,只不過(guò)都被設(shè)置為了0值,因?yàn)橐话銢](méi)人需要它的返回值。
Q1:objc_msgSend()匯編實(shí)現(xiàn)中的GetIsaSupport NORMAL似乎不會(huì)執(zhí)行啊,沒(méi)看懂。
對(duì)匯編的執(zhí)行流程不甚了解,真誠(chéng)求答。
Q2:對(duì)最后拿到的imp,arm64和x86_64為什么會(huì)有不同的處理方式?
arm64對(duì)最后的imp作了分類處里,而x86_64下則都是直接執(zhí)行imp,難道慢查找返回的imp都是真的sel的imp嗎?會(huì)不會(huì)是占位用的_objc_msgForward_impcache?
待回答。。。
(1)GetIsaFast NORMAL的匯編實(shí)現(xiàn)
.macro GetIsaFast //快速獲取Isa:receiver不是Tagged Pointer Object
.if $0 != STRET //對(duì)x86_64而言,LSB最低有效位為T(mén)agged Pointer bit。
testb $$1, %a1b //Z=(%a1b & 1)?1:0,receiver最低位為1?是就是Tagged Pointer,否則就是普通對(duì)象
PN
jnz LGetIsaSlow_f //Z != 0:即為1,說(shuō)明receiver為T(mén)agged Pointer Object
movq $$ ISA_MASK, %r10 //Z = 0:將ISA_MASK:0x00007ffffffffff8ULL賦值給%r10
andq (%a1), %r10 //%r10 = %r10 & isa, %r10成了類地址(常識(shí):打印一個(gè)對(duì)象就是打印對(duì)象地址)
.else
testb $$1, %a2b
PN
jnz LGetIsaSlow_f
movq $$ ISA_MASK, %r10
andq (%a2), %r10
.endif
LGetIsaDone:
.endmacro
//x86_64 Tagged Pointer Object的存儲(chǔ)和表示:receiver的最低有效位LSB為1。
//(1)basic tagged:receiver的低4位(含LSB)為slot,去_objc_debug_taggedpointer_classes中找isa;
//(2)extended tagged:receiver的第4~11位為slot,去_objc_debug_taggedpointer_ext_classes中找isa;
.macro GetIsaSupport //慢速獲取Isa:receiver是Tagged Pointer Object
LGetIsaSlow:
.if $0 != STRET
movl %a1d, %r11d //傳送receiver的低4字節(jié)至%r11d
.else
movl %a2d, %r11d
.endif
andl $$0xF, %r11d //%r11d = %r11d & 0xF 獲取receiver的低4位(basic tagged slot值)
// basic tagged
leaq _objc_debug_taggedpointer_classes(%rip), %r10 //%rip:存放當(dāng)前CPU正在執(zhí)行的指令的地址
movq (%r10, %r11, 8), %r10 // read isa from table 從basic tagged表中讀取receiver對(duì)應(yīng)的isa
leaq _OBJC_CLASS_$___NSUnrecognizedTaggedPointer(%rip), %r11 //slot為7(15)時(shí)對(duì)應(yīng)的保留類。
cmp %r10, %r11
jne LGetIsaDone_b //cmp結(jié)果不為0:說(shuō)明%r10即為receiver對(duì)應(yīng)的isa!
// extended tagged 否則,%r10不是一個(gè)真正的類!需要從extended tagged表中讀取receiver對(duì)應(yīng)的isa。
.if $0 != STRET
movl %a1d, %r11d //傳送receiver的低4字節(jié)至%r11d
.else
movl %a2d, %r11d
.endif
shrl $$4, %r11d //%r11d符號(hào)右移4位(去掉basic tagged的slot)
andl $$0xFF, %r11d //獲取receiver的第4~11(8位)的值(extended tagged slot)
leaq _objc_debug_taggedpointer_ext_classes(%rip), %r10
movq (%r10, %r11, 8), %r10 // read isa from table 從extended表中讀取receiver對(duì)應(yīng)的isa
jmp LGetIsaDone_b //不需要再比較了,因?yàn)闆](méi)有其他情況了!%r10即為receiver對(duì)應(yīng)的isa!
.endmacro
以上匯編等價(jià)于Runtime C內(nèi)聯(lián)函數(shù):
inline Class objc_object::getIsa()
{
//非TaggedPointer對(duì)象,直接獲取其isa
if (fastpath(!isTaggedPointer())) return ISA();
extern objc_class OBJC_CLASS_$___NSUnrecognizedTaggedPointer;
uintptr_t slot, ptr = (uintptr_t)this;
Class cls;
slot = (ptr >> _OBJC_TAG_SLOT_SHIFT) & _OBJC_TAG_SLOT_MASK;
// basic tagged
cls = objc_tag_classes[slot];
if (slowpath(cls == (Class)&OBJC_CLASS_$___NSUnrecognizedTaggedPointer)) {
slot = (ptr >> _OBJC_TAG_EXT_SLOT_SHIFT) & _OBJC_TAG_EXT_SLOT_MASK;
// extended tagged
cls = objc_tag_ext_classes[slot];
}
return cls;
}
(2)CacheLookup NORMAL, CALL, _objc_msgSend
.macro CacheHit
// r11 = found bucket!
.if $1 == GETIMP //一級(jí)
movq cached_imp(%r11), %rax // return imp
cmpq $$0, %rax
jz 9f // don't xor a nil imp
xorq %r10, %rax // xor the isa with the imp
9: ret
.else
.if $1 == CALL //一級(jí):objc_msgSend Normal,call %r10為類地址,%r11為_(kāi)cmd在buckets中的地址。
movq cached_imp(%r11), %r11 // load imp 拿到了imp!
xorq %r10, %r11 // xor imp and isa 應(yīng)該是反混淆
.if $0 != STRET //二級(jí)
// ne already set for forwarding by `xor`
.else
cmp %r11, %r11 // set eq for stret forwarding
.endif
jmp *%r11 // call imp ?。?!【最終直接發(fā)起調(diào)用】!??!
.elseif $1 == LOOKUP //一級(jí)
movq cached_imp(%r11), %r11
xorq %r10, %r11 // return imp ^ isa
ret
.else
.abort oops
.endif
.endif //這個(gè)endif貌似有點(diǎn)多余啊,它結(jié)束的是誰(shuí)???
.endmacro
.macro CacheLookup //實(shí)際上整個(gè)緩存至多遍歷一圈!因?yàn)閟el的值為0時(shí)便會(huì)直接跳至LCacheMiss_objc_msgSend!
//而這樣的情況總是會(huì)出現(xiàn)!因?yàn)閷?duì)x86_64而言,緩存達(dá)3/4便會(huì)擴(kuò)容!詳情cache_t::insert()函數(shù)。
LLookupStart$2:
.if $0 != STRET
movq %a2, %r11 // r11 = _cmd 即sel(一個(gè)地址值)
.else
movq %a3, %r11 // r11 = _cmd
.endif //小字節(jié)序:isa -> super -> cache -> (mask/_flags/_occupied)
andl 24(%r10), %r11d //r11 = r11 & class->cache.mask 獲取cache中的mask并執(zhí)行位&得到begin
shlq $$4, %r11 // r11 = offset = r11<<4 每個(gè)bucket占16字節(jié)
addq 16(%r10), %r11 // r11 = class->cache.buckets + offset sel的begin的地址。
// 匯編代碼下,是沒(méi)有數(shù)據(jù)類型的概念的,指針只是一個(gè)數(shù)字,加1就是加1,而不是加“一個(gè)單位”!
.if $0 != STRET
cmpq cached_sel(%r11), %a2 // if (bucket->sel != _cmd) cached_sel為0,cached_imp為8。
.else
cmpq cached_sel(%r11), %a3 // if (bucket->sel != _cmd)
.endif
jne 1f // scan more _cmd的地址與緩存中起始位置處的sel不相等!需繼續(xù)掃描!
CacheHit $0, $1 // call or return imp //相等則為緩存命中
1:
// loop 當(dāng)前bucket是否為cache的最后一個(gè)bucket:
cmpq $$1, cached_sel(%r11) //x86_64 Cache中的最后一個(gè)bucket的sel被置為1,imp指向第一個(gè)bucket
jbe 3f //YES(be:小于或等于)! if (bucket->sel <= 1) wrap(包裹) or miss(未擊中;錯(cuò)過(guò))
addq $$16, %r11 //NO, bucket++
2:
.if $0 != STRET
cmpq cached_sel(%r11), %a2 // if (bucket->sel != _cmd)
.else
cmpq cached_sel(%r11), %a3 // if (bucket->sel != _cmd)
.endif
jne 1b // scan more 往上跳
CacheHit $0, $1 // call or return imp
3: // wrap or miss wrap指的是Cache中的最后一個(gè)bucket“包裹”著第一個(gè)bucket!miss就是未占用的緩存bucket。
jb LCacheMiss$2 // if (bucket->sel < 1) cache miss :未占用的緩存bucket={0,0}
// LCacheMiss$2:LCacheMiss_objc_msgSend
// wrap 第一次遍歷到了Cache中的最后一個(gè)bucket,尚未遍歷一圈,還有可能緩存命中!請(qǐng)繼續(xù)遍歷cache!
movq cached_imp(%r11), %r11 //bucket->imp is really first bucket
//bucket->sel == 1:說(shuō)明是最后一個(gè)bucket,其imp指向第一個(gè)bucket
jmp 2f //往下跳!
1:
// loop 為了代碼的通用性,每次都要判斷當(dāng)前bucket是否為最后一個(gè)bucket。
cmpq $$1, cached_sel(%r11) //第二次比較:其主要目的看是否小于1,而不是等于1(因?yàn)椴粫?huì)再次等于1了)
jbe 3f // if (bucket->sel <= 1) wrap or miss
addq $$16, %r11 // >1:普通的bucket。 bucket++
2:
.if $0 != STRET
cmpq cached_sel(%r11), %a2 // if (bucket->sel != _cmd)
.else
cmpq cached_sel(%r11), %a3 // if (bucket->sel != _cmd)
.endif
jne 1b // scan more
CacheHit $0, $1 // call or return imp
3:
// double wrap or miss 第二次 wrap or miss 已遍歷一圈了,就不用再遍歷了!
jmp LCacheMiss$2 //對(duì)于x86_64而言,buckets中至少有一個(gè)bucket={0,0},所以來(lái)到這時(shí),
//只意味著bucket={0,0},未命中!
LLookupEnd$2:
.endmacro
以上匯編等價(jià)于C函數(shù):
IMP CacheLookup(Class cls, SEL _cmd){
cache_t cache = cls->cache;
bucket_t buckets = cache._buckets;
int bagin = _cmd & cache.mask; //如果_cmd被緩存,那么它會(huì)先嘗試放在buckets中的第bagin個(gè)位置。具體可看緩存的核心方法:void cache_t::insert(Class cls, SEL sel, IMP imp, id receiver)
bucket_t bucket = buckets[bagin];
while (_cmd != bucket.sel) {
switch ((uintptr_t)bucket.sel) {
case 0: //當(dāng)前bucket為空,直接宣布未命中!
return nil; //實(shí)際上為`LCacheMiss_objc_msgSend`,只不過(guò)這里做了分步處理,將其獨(dú)立出來(lái)了。
case 1: //當(dāng)前bucket為緩存中的最后一個(gè)bucket,即Mark end bucket;
bucket = bucket.imp; //最后一個(gè)bucket的imp為buckets,即緩存的開(kāi)始處。
break;
default:
bucket++;
break;
}
}
return bucket.imp;
}
注意:
(1)LCacheMiss$2中的$2為匯編調(diào)用中的第3個(gè)參數(shù),前兩個(gè)為$0、$1;這里的$2為匯編調(diào)用者_objc_msgSend,所以當(dāng)緩存命中失敗時(shí)會(huì)調(diào)用LCacheMiss_objc_msgSend,類似字符串拼接,哈哈哈。
(2)對(duì)非arm而言,如x86_64、arm64等:cache->buckets中的最后一個(gè)bucket為End marker(尾標(biāo)記),其bucket = {(SEL)(uintptr_t)1, (IMP)buckets},意味著buckets遍歷到頭了,同時(shí)也可快速繼續(xù)從頭遍歷;End marker可參看Runtime的buckets::allocateBuckets()函數(shù)。
(3)switch中case 0:或case 1:的情況至多出現(xiàn)一次!也即cache至多能遍歷一圈(而且還是capacity為4的情況),因?yàn)榫彺嬷锌偸谴嬖?code>{0,0}和{1,buckets}這兩個(gè)bucket,這是由緩存的設(shè)計(jì)機(jī)制所決定的。詳情請(qǐng)看cache_t::insert()函數(shù)(x86_64:緩存達(dá)到3/4capacity -1時(shí),再有方法進(jìn)行緩存時(shí)便會(huì)Double擴(kuò)容;arm64:緩存達(dá)到3/4*capacity時(shí)再有方法進(jìn)行緩存時(shí)便會(huì)Double擴(kuò)容)。