clang源码
源码:
LGTeacher *teacher = [LGTeacher alloc];
[teacher sayHello];
转换之后:
LGTeacher *teacher = ((LGTeacher *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("LGTeacher"), sel_registerName("alloc"));
((void (*)(id, SEL))(void *)objc_msgSend)((id)teacher, sel_registerName("sayHello"));
当我们对一个方法调用 进行 clang
源码之后 发现 方法的调用 底层实现为 objc_msgSend
函数 即 消息发送
objc_msgSend(id receiver, SEL op, ...)
第一个参数id receiver
为消息的接收者
第二个参数SEL op
为消息的名称SEL
...
为可变参数
objc_msgSend 汇编源码
通过源码进行搜查发现 objc_msgSend 函数
/********************************************************************
*
* id objc_msgSend(id self, SEL _cmd, ...);
* IMP objc_msgLookup(id self, SEL _cmd, ...);
*
* objc_msgLookup ABI:
* IMP returned in x17
* x16 reserved for our use but not used
*
********************************************************************/
为汇编实现 原因
1、 因objc_msgSend 为OC方法核心,调用极其频繁,出于性能考虑,这个函数内部是用汇编来实现。
2、方法参数的动态性,汇编调用函数时传递的参数是不确定的,那么发送消息时,直接调用一个函数就可以发送所有的消息。
源码分析
LGetIsaDone
ENTRY _objc_msgSend
UNWIND _objc_msgSend, NoFrame
///判断 receiver是否存才?p0 第一个参数
cmp p0, #0 // nil check and tagged pointer check
/// 是否支持 taggedPointers对象
#if SUPPORT_TAGGED_POINTERS
/// 是否为空或者小对象类型 (为空 返回 不为空 获取小对象的isa)
b.le LNilOrTagged // (MSB tagged pointer looks negative)
/// 不是小对象类型
#else
/// 直接返回
b.eq LReturnZero
#endif
/// receiver存才 从寄存器x0中获取 isa存入 p13
ldr p13, [x0] // p13 = isa
/// 获取 Class
GetClassFromIsa_p16 p13 // p16 = class
/// 获取 isa完毕
LGetIsaDone:
/// 开启缓存查找流程即快速查找流程
// calls imp or objc_msgSend_uncached
CacheLookup NORMAL, _objc_msgSend
#if SUPPORT_TAGGED_POINTERS
LNilOrTagged:
/// 为nil 直接返回
b.eq LReturnZero // nil check 直接返回
/// 小对象类型 获取 isa
// tagged
adrp x10, _objc_debug_taggedpointer_classes@PAGE
add x10, x10, _objc_debug_taggedpointer_classes@PAGEOFF
ubfx x11, x0, #60, #4
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
首先判断objc_msgSend
第一个参数(id receiver
为消息的接收者
)是否存再,不存在判断是否是支持小对象类型(taggedPointers
)的参数 是获取小对象isa
不是 返回空
receiver
存在 获取 消息接收者的isa
根据isa
获取 class
可能 类对象
可能是元类
CacheLookup NORMAL, _objc_msgSend
/********************************************************************
*
* CacheLookup NORMAL|GETIMP|LOOKUP <function>
*
* 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, x17
*
* On exit: (found) calls or returns IMP
* with x16 = class, x17 = IMP
* (not found) jumps to LCacheMiss
*
********************************************************************/
.... 有省略
LLookupStart$1:
/// isa首地址 平移16字节 获取 cache_t cache = 高16位 msak + 低48位buckets
// p1 = SEL, p16 = isa首地址
ldr p11, [x16, #CACHE] // p11 = mask|buckets
/// arm 64
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
/// 通过chche&掩码将高位mask抹零、得到buckets
and p10, p11, #0x0000ffffffffffff // p10 = buckets
/// 通过掩码mask 获取 哈希下标
and p12, p1, p11, LSR #48 // x12 = _cmd & mask
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
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
/// p12是获取到的下标,然后逻辑左移4位,再由p10(buckets)平移,得到对应的bucket保存到p12中
add p12, p10, p12, LSL #(1+PTRSHIFT)
// p12 = buckets + ((_cmd & mask) << (1+PTRSHIFT))
/// 将p12属性imp 和 sel分别赋值为p17 和 p9
ldp p17, p9, [x12] // {imp, sel} = *bucket
/// 比较 bucket的 sel 是否等于 传入的 sel
1: cmp p9, p1 // if (bucket->sel != _cmd)
/// 不等于 跳到 2
b.ne 2f// scan more
/// 如果相同 直接返回 imp 找到了
CacheHit $0 // call or return imp
2: // not hit: p12 = not-hit bucket
/// 判断当前bucket的sel 是否为0 为0 往下走 不为 0 (cbz p9, __objc_msgSend_uncached) 开始慢速查找流程
CheckMiss $0 // miss if bucket->sel == 0
/// 判断 当前的bucket 是否 为 buckets的第一个元素
cmp p12, p10 // wrap if bucket == buckets
/// 是的话 直接 跳 3
b.eq 3f
/// 不相等,以x12即当前bucket地址往前移动一个bucket大小为地址读取其中的值,并赋值为p17和p9,同时x12=x12-BUCKET_SIZE
ldp p17, p9, [x12, #-BUCKET_SIZE]! // {imp, sel} = *--bucket
/// 跳到 1 循环
b 1b // loop
3: // wrap: p12 = first bucket, w11 = mask
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
/// 接上步骤,如果p12和buckets数组第一个相等,需要移动到buckets数组的最后一位
add p12, p12, p11, LSR #(48 - (1+PTRSHIFT))
// p12 = buckets + (mask << 1+PTRSHIFT)
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
add p12, p12, p11, LSL #(1+PTRSHIFT)
// p12 = buckets + (mask << 1+PTRSHIFT)
#else
#error Unsupported cache mask storage for ARM64.
#endif
// Clone scanning loop to miss instead of hang when cache is corrupt.
// The slow path may detect any corruption and halt later.
/// 通过bucket结构体得到imp-sel 将p12属性imp 和 sel分别赋值为p17 和 p9
ldp p17, p9, [x12] // {imp, sel} = *bucket
/// 比较 bucket的 sel 是否等于 传入的 sel
1: cmp p9, p1 // if (bucket->sel != _cmd)
/// 不等于 跳2
b.ne 2f // scan more
/// 等于 命中 直接返回 imp
CacheHit $0 // call or return imp
2: // not hit: p12 = not-hit bucket
/// 判断当前bucket的sel 是否为0 为0 往下走 不为 0 (cbz p9, __objc_msgSend_uncached) 开始慢速查找流程
CheckMiss $0 // miss if bucket->sel == 0
/// 判断 当前的bucket 是否 为 buckets的第一个元素
cmp p12, p10 // wrap if bucket == buckets
/// 是 跳 3
b.eq 3f
/// 从最后一个元素往前查找
ldp p17, p9, [x12, #-BUCKET_SIZE]! // {imp, sel} = *--bucket
b 1b // loop
LLookupEnd$1:
LLookupRecover$1:
3: // double wrap
/// 跳出 去往慢速查询流程 b __objc_msgSend_uncached
JumpMiss $0
.endmacro
通过 平移 获取 cache 匹配传进来的 sel 找到 返回imp 没有 进入慢速流程
网友评论