重学OC第七篇:方法查找(上)--汇编实现

前言

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的调用流程
    1. 从isa中得到类
    2. 调用CacheLookup NORMAL, _objc_msgSend
    3. 有缓存直接返回imp执行,没有就会进入lookUpImpOrForward。
  • objc_msgSendSuper调用的还是self,但是父类是第一搜索类。

参考文章:
认识ARM64汇编
OS X Assembler Reference

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值