目录
前言
OC的方法调用用clang转为c++后就是调用objc_msgSend,在objc源码中搜索objc_msgSend在message.h文件中只找到声明
OBJC_EXPORT void
objc_msgSend(void /* id self, SEL op, ... */ )
OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
OBJC_EXPORT void
objc_msgSendSuper(void /* struct objc_super *super, SEL op, ... */ )
OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
在objc-msg-arm64.s文件中找到了汇编的实现。为什么用汇编实现?为了性能,为了快,什么参数不确定性。。。。。。不会汇编,能理解快,理解不了参数不确定性改用汇编后到底提升了多少性能。
文中源码只涉及arm64 && __LP64__部分。
一、objc_msgSend
1.1 从isa中得到类
ENTRY _objc_msgSend //_objc_msgSend入口
UNWIND _objc_msgSend, NoFrame
//p0与立即数0作比较
cmp p0, #0 // nil check and tagged pointer check
#if SUPPORT_TAGGED_POINTERS
//b表示跳转,le表示带符号的小于或等于
b.le LNilOrTagged // (MSB tagged pointer looks negative) 比较结果是小于等于0
#else
b.eq LReturnZero
#endif
ldr p13, [x0] // p13 = isa
GetClassFromIsa_p16 p13 // p16 = class
LGetIsaDone:
// calls imp or objc_msgSend_uncached
CacheLookup NORMAL, _objc_msgSend
#if SUPPORT_TAGGED_POINTERS
LNilOrTagged:
b.eq LReturnZero // nil check
// tagged 得到有全局变量tagged地址的4KB页的基址,adrp就是将该页的基址存到寄存器X10中
adrp x10, _objc_debug_taggedpointer_classes@PAGE
//x10是页的基址,x10 + tagged@PAGEOFF拿到tagged的地址,存到x10中
add x10, x10, _objc_debug_taggedpointer_classes@PAGEOFF
ubfx x11, x0, #60, #4 //从x0的第60位开始,取4位到x11中,剩余高位用0填充
ldr x16, [x10, x11, LSL #3]
adrp x10, _OBJC_CLASS_$___NSUnrecognizedTaggedPointer@PAGE
add x10, x10, _OBJC_CLASS_$___NSUnrecognizedTaggedPointer@PAGEOFF
cmp x10, x16
b.ne LGetIsaDone
// ext tagged
adrp x10, _objc_debug_taggedpointer_ext_classes@PAGE
add x10, x10, _objc_debug_taggedpointer_ext_classes@PAGEOFF
ubfx x11, x0, #52, #8
ldr x16, [x10, x11, LSL #3]
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
先比较p0与0, 如果支持tagged pointer,p0 <= 0就跳转去LNilOrTagged,否则p0 = 0时就跳转去LReturnZero, 可以看到在LnilOrTagged中当p0 = 0时也会调用LReturnZero,如果p0 < 0,就会进入tagged pointer的一些操作中,然后跳回LGetIsaDone,从x0中读出isa放到p13, 用p13 & mask拿到真正的类地址存入p16。
然后执行CacheLookup NORMAL, _objc_msgSend
//TODO: LnilOrTagged中涉及到tagged pointer的知识没弄明白这段。。。需要去深入了解下tagged pointer。
1.2 CacheLookup
从代码注释中可以知道它的用法为:
CacheLookup NORMAL|GETIMP|LOOKUP
它的作用就是在类的方法缓存中通过sel去查找imp,x1中是sel, x16中是要查找的类,如果找到了就把imp放入x17中,把对应的类放入x16; 如果没找到,就进入JumpMiss。
1.2.1 CacheLookup NORMAL, _objc_msgSend
#define CACHE (2 * __SIZEOF_POINTER__)
.macro CacheLookup
LLookupStart$1:
// p1 = SEL, p16 = isa 通过isa偏移16得到类的cache地址存入p11,p11为_maskAndBuckets
ldr p11, [x16, #CACHE] // p11 = mask|buckets
//通过_maskAndBuckets & bucketsMask得到bucktes
and p10, p11, #0x0000ffffffffffff // p10 = buckets
//通过_maskAndBuckets >> 48拿到mask,然后用sel & mask存入x12
and p12, p1, p11, LSR #48 // x12 = _cmd & mask
//通过buckets首地址偏移 + begin * 16拿到sel在buckets中的地址,16是bucket_t的类型大小,begin是sel & mask后得到的0~mask的一个随机数
add p12, p10, p12, LSL #(1+PTRSHIFT) // p12 = buckets + ((_cmd & mask) << (1+PTRSHIFT))
//从x12中读出bucket,把imp放入p17,sel放入p9
ldp p17, p9, [x12] // {imp, sel} = *bucket
1: cmp p9, p1 // if (bucket->sel != _cmd)
//不相等进入步骤2, f代表向前,b代表向回
b.ne 2f // scan more
//TailCallCachedImp x17, x12, x1, x16 验证并调用imp,TailCallCachedImp也是个宏,看不懂里面的汇编,按注释走
CacheHit $0 // call or return imp
2: // not hit: p12 = not-hit bucket
//cbz p9, __objc_msgSend_uncached p9为0就跳转uncached方法,调用MethodTableLookup,跳转进_lookUpImpOrForward
CheckMiss $0 // miss if bucket->sel == 0
//找到一个sel不为0的bucket,用这个bucket与buckets比较
cmp p12, p10 // wrap if bucket == buckets
//如果bucket等于buckets,就进入步骤3
b.eq 3f
//不相等,bucket--查找下一个
ldp p17, p9, [x12, #-BUCKET_SIZE]! // {imp, sel} = *--bucket
//跳回步骤1进行循环
b 1b // loop
3: // wrap: p12 = first bucket, w11 = mask
//mask右移48 - 4位,相当于mask最后左移了4位,bucktes + mask * 16,意思就是p12 = bucktes中最后一个元素地址
add p12, p12, p11, LSR #(48 - (1+PTRSHIFT))
// p12 = buckets + (mask << 1+PTRSHIFT)
// Clone scanning loop to miss instead of hang when cache is corrupt.
// The slow path may detect any corruption and halt later.
ldp p17, p9, [x12] // {imp, sel} = *bucket
1: cmp p9, p1 // if (bucket->sel != _cmd)
b.ne 2f // scan more
CacheHit $0 // call or return imp
2: // not hit: p12 = not-hit bucket
CheckMiss $0 // miss if bucket->sel == 0
cmp p12, p10 // wrap if bucket == buckets
b.eq 3f
ldp p17, p9, [x12, #-BUCKET_SIZE]! // {imp, sel} = *--bucket
b 1b // loop
LLookupEnd$1:
LLookupRecover$1:
3: // double wrap
JumpMiss $0
.endmacro
<1> 通过从_maskAndBukects中取出buckets和mask, 通过_cmd & mask算出在buckets中的最可能的位置,然后偏移到这个位置取出存在这里的bucket。
<2> 用bucket.sel与_cmd比较,如果相等,说明命中缓存,通过TailCallCachedImp对地址作验证后调用imp,结束。如果不相等,进入<3>。
<3> CheckMiss NORMAL, 调用cbz p9, __objc_msgSend_uncached, 如果p9也就是bucket.sel为0就跳转__objc_msgSend_uncached,在其中调用MethodTableLookup,跳转进_lookUpImpOrForward。(这一步我的理解是如果bucket.sel为0就可以直接说明缓存中没有_cmd,所以就直接去类的方法列表中找去了)。如果不为0,说明在存的时候出现了哈希冲突,位置被占了,然后就判断bucket是不是在buckets的首位,如果是进入<4>; 如果不是,进行bucket自减1,然后循环跳回到<2>。
<4> 通过buckets偏移mask个bucket大小拿到最后一个元素放入p12, 然后开始进行循环查找,循环内容同<2>+<3>一样。如果还是没找到就进入JumpMiss, 直接调用__objc_msgSend_uncached后会调用lookUpImpOrForward去类的方法列表中查找。
//TODO:这里应该整个图,容易清晰点,一点汇编不会扣的脑瓜子疼…
二、objc_msgSendSuper
ENTRY _objc_msgSendSuper
UNWIND _objc_msgSendSuper, NoFrame
ldp p0, p16, [x0] // p0 = real receiver, p16 = class
// calls imp or objc_msgSend_uncached
CacheLookup NORMAL, _objc_msgSendSuper
END_ENTRY _objc_msgSendSuper
// no _objc_msgLookupSuper
ENTRY _objc_msgSendSuper2
UNWIND _objc_msgSendSuper2, NoFrame
ldp p0, p16, [x0] // p0 = real receiver, p16 = class
ldr p16, [x16, #SUPERCLASS] // p16 = class->superclass
CacheLookup NORMAL, _objc_msgSendSuper2
END_ENTRY _objc_msgSendSuper2
struct objc_super {
/// Specifies an instance of a class.
__unsafe_unretained _Nonnull id receiver;
__unsafe_unretained _Nonnull Class super_class;
/* super_class is the first class to search */
};
OBJC_EXPORT void
objc_msgSendSuper(void /* struct objc_super *super, SEL op, ... */ )
OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
OBJC_EXPORT id _Nullable
objc_msgSendSuper2(struct objc_super * _Nonnull super, SEL _Nonnull op, ...)
OBJC_AVAILABLE(10.6, 2.0, 9.0, 1.0, 2.0);
从声明中可以看出objc_msgSendSuper的第一个参数是一个objc_super的结构体类型。所以调用是这样的
objc_msgSendSuper({(id)self, (id)class_getSuperclass(objc_getClass("Test"))}, sel_registerName("init"))
从objc_super注释中可以看出super_class是第一搜索类,但是从汇编中看_objc_msgSendSuper直接调用的class, 所以c++的代码中通过class_getSuperclass拿到父类传给的class, 而_objc_msgSendSuper2是通过从类的首地址偏移8个字节取到的superclass,_objc_msgSendSuper2是在哪用的呢?
总结
- objc_msgSend的调用流程
- 从isa中得到类
- 调用CacheLookup NORMAL, _objc_msgSend
- 有缓存直接返回imp执行,没有就会进入lookUpImpOrForward。
- objc_msgSendSuper调用的还是self,但是父类是第一搜索类。