OC - Runimte & objc_msgSend()-下

322 阅读3分钟

前言

通过 OC - Runimte & objc_msgSend() 文章我们对Runtime 有了初步了解。知道了Runtime编译时和运行时的区别,以及三种调用方式。最后我还对objc_msgSend 进行了初步分析。

objc_msgSend 汇编代码分析

这篇文章我们继续对 objc_msgSend汇编代码进行分析。上一篇文章我们分析到 CacheLookup

.macro CacheLookup Mode, Function, MissLabelDynamic, MissLabelConstant
    
    mov	x15, x16			// stash the original isa  // 将x16的值,赋值给x15
LLookupStart\Function:
    // p1 = SEL, p16 = isa  // => isa -> 类的 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
    // x16(isa) 平移 16 就得到了 cache的地址(即为_bucketsAndMaybeMask的地址)
    // 将 _bucketsAndMaybeMask的地址存入 p11 寄存器中。
    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 = _bucketsAndMaybeMask & #0x0000fffffffffffe
    // 如果_bucketsAndMaybeMask第 0 位不等于 0,就跳转到 LLookupPreopt\Function
    tbnz	p11, #0, LLookupPreopt\Function
#endif
    eor	p12, p1, p1, LSR #7
    // 通过哈希求 index 下标
    and	p12, p12, p11, LSR #48		// x12 = (_cmd ^ (_cmd >> 7)) & mask
#else
    // p11 = cache (按位与)-> p10 = buckets
    and	p10, p11, #0x0000ffffffffffff	// p10 = buckets
    // p11, LSR #48 => 得到 mask 的值
    // p1(_cmd) & mask => index(p12) 
    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
    // p11 = cache (按位与)-> p10 = buckets
    // p1(_cmd) & mask => index(p12) 
    // PTRSHIFT = 3 (全局搜索)
    // (_cmd & mask) << (1+PTRSHIFT) ->  (_cmd & mask) << 4
    // buckets + 内存平移(1,2,3,4)
    // 就得到了 b[i](位移平移) b[i] -> b + i
    // p13 当前要查找的 bucket
    add	p13, p10, p12, LSL #(1+PTRSHIFT)
                            // p13 = buckets + ((_cmd & mask) << (1+PTRSHIFT))

                            // do {
    // [x13]取 bucket 长度单位 -> *bucket--
    // 拿到 bucket 里面的东西 imp(p17) 和 sel(p9)
    // 查到的 sel(p9) 和 我们要查的 SEL(p1) 进行比较
    // 如果一样就继续往下走,走到 2 里面,CacheHit缓存命中。 如果不等于就走到 3f 里面。其实就是一个循环,直到找到为止。
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;
    // p10(当前的第一个地址)和 p13 进行比较。如果 p13 大于 p10,就继续进行平移。再哈希
    cmp	p13, p10			// } while (bucket >= buckets)
    b.hs     1b

PTRSHIFT 的定义

#define PTRSHIFT 3  // 1<<PTRSHIFT == PTRSIZE

CacheHit 缓存命中

// CacheHit: x17 = cached IMP, x10 = address of buckets, x1 = SEL, x16 = isa
.macro CacheHit
// NORMAL 调用 CacheLookup 方法并带入 NORMAL 参数
.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

CacheHit 中,通过 if $0 == NORMAL的判断,进入到了 TailCallCachedImp 中。

//  CacheHit: x17 = cached IMP, x10 = address of buckets, x1 = SEL, x16 = isa
.macro TailCallCachedImp
    // $0 = cached imp, $1 = address of cached imp, $2 = SEL, $3 = isa
    // $0(x17) ^  $3(isa) = 编码(imp)
    // call imp
    eor	$0, $0, $3
    // 跳转响应的 imp
    br	$0
.endmacro

总结

这就是 objc_msgSend 通过 selimp 的过程。

    1. 判断 recevier 是否存在
    1. 通过 recevier 找到 isa 找到 class(p16)
    1. class 内存平移得到 cache,找到 bucketmask
    1. 通过 bucket按位左移右移操作(mask)操作得到 bucket(bucket 掩码得到 bucket)
    1. 通过 mask掩码得到mask
    1. insert哈希函数(mask_t)(value & mask);
    1. 获取第一次查找哈希下标index
    1. 通过哈希下标index进行平移,把 bucket 的首地址 + index 得到整个缓存里的第几个 bucket
    1. 得到 bucket 中存储的 impsel
  • 10.拿到的 sel 与我们查找的 sel(_cmd) 进行比较。
    1. 如果相等。就 cacheHit(缓存命中),就拿到了对应的 imp 按位异或 isa 就得到了真正 imp 的地址。 然后 br调用imp
    1. 如果不相等。就 bucket-- 继续平移查找。相当于一个“死循环” 变量!
    1. 如果完全找不到。就会走的 MissLabelDynamic(__objc_msgSend_uncached)