7-- Runtime & objc_msgSend

cache 讀寫流程

下圖是cache的讀寫流程,當咱們讀取cache的時候,須要調用cache_getImp image.png 本文的重點在objc_msgSend,是發起讀取cache的地方.html

runtime

編譯時 顧名思義就是正在編譯的時候,就是編譯器幫你把源代碼翻譯成機器能識別的代碼。(固然這只是一意義上這麼說,實際上可能只是翻譯成某個中間狀態的語音。)好比靜態類型檢查之類的。c++

運行時 顧名思義就是在代碼運行起來後,被裝載到內存中。(當你的代碼保存在磁盤中,沒有寫入內存以前都是「死」代碼,只有寫入內存中才變成「活」代碼。並且運行時類型檢查與編譯時類型檢查(或者靜態類型檢查)是不同的,不是簡單的掃描代碼,而是在內存中作了一些操做和判斷。 詳見apple runtimegit

runtime調用的三種方式:github

  1. Objective-C 方法調用
  2. NSObject提供的API
  3. Runtime 底層API,objc_msg_send方法

image.png

@interface MLTeacher : NSObject
- (void)sayHello;
@end
@implementation MLTeacher
- (void)sayHello{
    NSLog(@"666 %s",__func__);
}
@end
@interface MLPerson : MLTeacher
- (void)sayHello;
- (void)sayNB;
@end
@implementation MLPerson
- (void)sayNB{
    NSLog(@"666");
}
@end
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        MLPerson *person = [MLPerson alloc];
        MLTeacher *teach = [MLTeacher alloc];
        [person sayNB];
        [person sayHello];
        NSLog(@"Hello, World!");
    }
    return 0;
}

複製代碼

經過命令clang -rewrite-objc main.m -o main.cpp 生成cpp文件緩存

image.png 能夠發現sayNB之類的方法調用都是經過objc_msgSend來實現的。markdown

那麼咱們是否可以直接調用objc_msgSend方法呢?咱們調用的時候可能會編譯失敗,須要將Build Settings -> Enable Strict Checking of objc_msgSend Calls 設置成爲 NO, 以下圖 image.png 引入頭文件#import <objc/message.h>代碼以下app

MLPerson *person = [MLPerson alloc];
objc_msgSend(person, @selector(sayNB));
複製代碼

若是咱們重載方法,調用supercpp的實現會是什麼樣呢?ide

image.png

static void _I_MLPerson_sayHello(MLPerson * self, SEL _cmd) {
    ((void (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("MLPerson"))}, sel_registerName("sayHello"));
}
複製代碼

發現cpp的實現是經過調用objc_msgSendSuper實現的. 經過上述的分析,以及對runtime的體驗,方法的本質就是objc_msgSend.oop

objc_msgSend 分析

當在源碼中全局搜索objc_msgSend的時候,發現objc_msgSend的實現是彙編 以下是在arm64objc_msgSend的彙編實現 image.png 此時有個疑問是爲何使用匯編實現的,而不是經過c++的代碼實現的?ui

line 54:cmp p0 #0 //p0 是receiver的地址,對p0 與 0 比較,若是爲nil直接返回 line 56 或者 line 58

line 60: 將[x0]的內容讀取到p13, [x0]是 isa

line 61: p16 賦值爲 isa,下圖爲GetClassFromIsa_p16源碼 image.png image.png

實際就是指針地址與ISA_MASK, 獲取到isa的地址,獲取isa的地址是要獲取cache, 先從cache中找到IMP. line 64: CacheLookup

1. .macro CacheLookup Mode, Function, MissLabelDynamic, MissLabelConstant
2. 
3. 	mov	x15, x16 // stash the original isa, x16:isa
4. LLookupStart\Function:
5. 	// p1 = SEL, p16 = isa
6. #if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS
7. 	ldr	p10, [x16, #CACHE] // p10 = mask|buckets, CACHE: 16; p10 = x16 + 16, cache_t
8. 	lsr	p11, p10, #48			// p11 = mask
9. 	and	p10, p10, #0xffffffffffff	// p10 = buckets
10. 	and	w12, w1, w11			// x12 = _cmd & mask
11. #elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
12. 	ldr	p11, [x16, #CACHE] // p11 = mask|buckets, CACHE: 16; p10 = x16 + 16, cache_t
13. #if CONFIG_USE_PREOPT_CACHES
14. #if __has_feature(ptrauth_calls)
15. 	tbnz	p11, #0, LLookupPreopt\Function
16. 	and	p10, p11, #0x0000ffffffffffff	// p10 = buckets
17. #else
18. 	and	p10, p11, #0x0000fffffffffffe	// p10 = buckets
19. 	tbnz	p11, #0, LLookupPreopt\Function
20. #endif
21. 	eor	p12, p1, p1, LSR #7
22. 	and	p12, p12, p11, LSR #48		// x12 = (_cmd ^ (_cmd >> 7)) & mask
23. #else
24. 	and	p10, p11, #0x0000ffffffffffff	// p10 = buckets
25. 	and	p12, p1, p11, LSR #48		// x12 = _cmd & mask
26. #endif // CONFIG_USE_PREOPT_CACHES
27. #elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
28. 	ldr	p11, [x16, #CACHE]				// p11 = mask|buckets
29. 	and	p10, p11, #~0xf			// p10 = buckets
30. 	and	p11, p11, #0xf			// p11 = maskShift
31. 	mov	p12, #0xffff
32. 	lsr	p11, p12, p11			// p11 = mask = 0xffff >> p11
33. 	and	p12, p1, p11			// x12 = _cmd & mask
34. #else
35. #error Unsupported cache mask storage for ARM64.
36. #endif
37. 
38. 	add	p13, p10, p12, LSL #(1+PTRSHIFT)
39. 						// p13 = buckets + ((_cmd & mask) << (1+PTRSHIFT))
40. 
41. 						// do {
42. 1:	ldp	p17, p9, [x13], #-BUCKET_SIZE	//     {imp, sel} = *bucket--
43. 	cmp	p9, p1				//     if (sel != _cmd) {
44. 	b.ne	3f				//         scan more
45. 						//     } else {
46. 2:	CacheHit \Mode				// hit:    call or return imp
47. 						//     }
48. 3:	cbz	p9, \MissLabelDynamic		//     if (sel == 0) goto Miss;
49. 	cmp	p13, p10			// } while (bucket >= buckets)
50. 	b.hs	1b
51. 
52. 	// wrap-around:
53. 	//   p10 = first bucket
54. 	//   p11 = mask (and maybe other bits on LP64)
55. 	//   p12 = _cmd & mask
56. 	//
57. 	// A full cache can happen with CACHE_ALLOW_FULL_UTILIZATION.
58. 	// So stop when we circle back to the first probed bucket
59. 	// rather than when hitting the first bucket again.
60. 	//
61. 	// Note that we might probe the initial bucket twice
62. 	// when the first probed slot is the last entry.
63. 
64. 
65. #if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS
66. 	add	p13, p10, w11, UXTW #(1+PTRSHIFT)
67. 						// p13 = buckets + (mask << 1+PTRSHIFT)
68. #elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
69. 	add	p13, p10, p11, LSR #(48 - (1+PTRSHIFT))
70. 						// p13 = buckets + (mask << 1+PTRSHIFT)
71. 						// see comment about maskZeroBits
72. #elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
73. 	add	p13, p10, p11, LSL #(1+PTRSHIFT)
74. 						// p13 = buckets + (mask << 1+PTRSHIFT)
75. #else
76. #error Unsupported cache mask storage for ARM64.
77. #endif
78. 	add	p12, p10, p12, LSL #(1+PTRSHIFT)
79. 						// p12 = first probed bucket
80. 
81. 						// do {
82. 4:	ldp	p17, p9, [x13], #-BUCKET_SIZE	//     {imp, sel} = *bucket--
83. 	cmp	p9, p1				//     if (sel == _cmd)
84. 	b.eq	2b				//         goto hit
85. 	cmp	p9, #0				// } while (sel != 0 &&
86. 	ccmp	p13, p12, #0, ne		//     bucket > first_probed)
87. 	b.hi	4b
88. 
89. LLookupEnd\Function:
90. LLookupRecover\Function:
91. 	b	\MissLabelDynamic
92. 
93. #if CONFIG_USE_PREOPT_CACHES
94. #if CACHE_MASK_STORAGE != CACHE_MASK_STORAGE_HIGH_16
95. #error config unsupported
96. #endif
97. LLookupPreopt\Function:
98. #if __has_feature(ptrauth_calls)
99. 	and	p10, p11, #0x007ffffffffffffe	// p10 = buckets
100. 	autdb	x10, x16			// auth as early as possible
101. #endif
102. 
103. 	// x12 = (_cmd - first_shared_cache_sel)
104. 	adrp	x9, _MagicSelRef@PAGE
105. 	ldr	p9, [x9, _MagicSelRef@PAGEOFF]
106. 	sub	p12, p1, p9
107. 
108. 	// w9  = ((_cmd - first_shared_cache_sel) >> hash_shift & hash_mask)
109. #if __has_feature(ptrauth_calls)
110. 	// bits 63..60 of x11 are the number of bits in hash_mask
111. 	// bits 59..55 of x11 is hash_shift
112. 
113. 	lsr	x17, x11, #55			// w17 = (hash_shift, ...)
114. 	lsr	w9, w12, w17			// >>= shift
115. 
116. 	lsr	x17, x11, #60			// w17 = mask_bits
117. 	mov	x11, #0x7fff
118. 	lsr	x11, x11, x17			// p11 = mask (0x7fff >> mask_bits)
119. 	and	x9, x9, x11			// &= mask
120. #else
121. 	// bits 63..53 of x11 is hash_mask
122. 	// bits 52..48 of x11 is hash_shift
123. 	lsr	x17, x11, #48			// w17 = (hash_shift, hash_mask)
124. 	lsr	w9, w12, w17			// >>= shift
125. 	and	x9, x9, x11, LSR #53		// &=  mask
126. #endif
127. 
128. 	ldr	x17, [x10, x9, LSL #3]		// x17 == sel_offs | (imp_offs << 32)
129. 	cmp	x12, w17, uxtw
130. 
131. .if \Mode == GETIMP
132. 	b.ne	\MissLabelConstant		// cache miss
133. 	sub	x0, x16, x17, LSR #32		// imp = isa - imp_offs
134. 	SignAsImp x0
135. 	ret
136. .else
137. 	b.ne	5f				// cache miss
138. 	sub	x17, x16, x17, LSR #32		// imp = isa - imp_offs
139. .if \Mode == NORMAL
140. 	br	x17
141. .elseif \Mode == LOOKUP
142. 	orr x16, x16, #3 // for instrumentation, note that we hit a constant cache
143. 	SignAsImp x17
144. 	ret
145. .else
146. .abort  unhandled mode \Mode
147. .endif
148. 
149. 5:	ldursw	x9, [x10, #-8]			// offset -8 is the fallback offset
150. 	add	x16, x16, x9			// compute the fallback isa
151. 	b	LLookupStart\Function		// lookup again with a new isa
152. .endif
153. #endif // CONFIG_USE_PREOPT_CACHES
154. 
155. .endmacro
複製代碼

上述彙編的流程以下:

  1. Line 3: x15, x16isa存儲到x15
  2. Line 12: ldr p11, [x16, #CACHE], 其中CACHE = 16, p11 = x16+16, 由class結構可知, p11cache_t的首地址
  3. Line 18: and p10, p11, #0x0000fffffffffffe, p10 = p11 & 00x0000fffffffffffe, 0x0000fffffffffffebuckets的掩碼, 獲得p10 = buckets 首地址.
  4. Line 18: p11, #0, LLookupPreopt\Function, 不爲0就跳轉到LLookupPreopt
  5. Line 38: add p13, p10, p12, LSL #(1+PTRSHIFT), p12爲當前sel, impindex, p13 = buckets + (_cmd & mask) << (1 + PTRSHIFT). PTRSHIFT3. 右移4位的緣由,是要知道buckets內存平移須要平移幾個單位. 即buckets[i]. p13爲當前要查找的bucket.
  6. Line 42 ~ 44: 1:ldp p17, p9, [x13], #-BUCKET_SIZE, 查找到當前位置存不存在bucket. 若是找到就跳轉到Cachehit,緩存命中,不然的話,就循環查找. 若是找到,就callimp.

從上述源碼中咱們能夠大概瞭解到objc_msgSend的流程爲以下:

  1. 經過receiver獲取到isa
  2. isa獲取cache
  3. cache(包含buckets & mask), 經過buckets的掩碼獲得buckets.
  4. 經過mask掩碼獲得mask(mask 爲當前bucketscapacipty -1).
  5. sel imp進行hash, 獲得第一次查找的index
  6. buckets + index整個緩存中的第幾個bucket
  7. 獲得查找到的sel imp與傳進來的sel == _cmd進行比較, 若是相等,緩存命中,調用imp
  8. 若是不想等, buckets + index --,向前查找,跳轉到step 7
  9. 若是整個buckets遍歷結束,都沒有找到, 跳轉到objc_msgSend_uncached
  10. objc_msgSend_uncached會在後續的文章更新...

image.png

補充

bt:查看堆棧

LLVM 源碼地址

相關文章
相關標籤/搜索