Cache Read/write Process

The following figure shows the cache read/write process. When we read the cache, we need to callcache_getImp The focus of this article isobjc_msgSendIs to initiate a readcachePlace.

runtime

Compile-time, as the name implies, is compilation time, when the compiler translates your source code into code that the machine can recognize. (Of course, this is only in the sense that it might actually be translated into some intermediate state of speech.) Things like static type checking.

Runtime is, as the name implies, loaded into memory after the code has been run. When your code is stored on disk, it is “dead” until it is written to memory, and “alive” until it is written to memory. And unlike compile-time type checking (or static type checking), runtime type checking does not simply scan code, but does some operations and judgments in memory. See the apple for the runtime

The Runtime can be called in three ways:

  1. Objective-c method calls
  2. NSObject provides an API
  3. Runtime low-level API, objc_MSG_send method

@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; }Copy the code

To generate a CPP file, run the clang-rewrite-objc main.m -o main. CPP command

You can findsayNBAnd so onobjc_msgSendTo achieve.

So can we just call itobjc_msgSendMethod? We may fail to compile when we callBuild Settings -> Enable Strict Checking of objc_msgSend CallsSet to becomeNO, the following figureImporting header files#import <objc/message.h>The following code

MLPerson *person = [MLPerson alloc];
objc_msgSend(person, @selector(sayNB));
Copy the code

If we override the method and call super, what would the implementation of CPP look like?

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"));
}
Copy the code

It was discovered that the implementation of CPP was implemented by calling objc_msgSendSuper. Based on the above analysis and experience with the Runtime, the essence of the method is objc_msgSend.

Objc_msgSend analysis

When searching globally in source codeobjc_msgSendWhen foundobjc_msgSendThe implementation is compiled as follows inarm64In theobjc_msgSendAssembly implementation ofWhy was it implemented in assembly rather than c++ code?

Line 54: CMP p0 #0 //p0 is the address of the receiver. If p0 is nil, return line 56 or line 58

Line 60: read from [x0] to p13, where [x0] is ISA

Line 61: p16 is set to ISA, as shown belowGetClassFromIsa_p16The source code

Imp. line 64: CacheLookup (” isa “, “isa”, “isa”)

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
Copy the code

The process of the above compilation is as follows:

  1. Line 3: x15, x16willisaStored in thex15
  2. Line 12: ldr p11, [x16, #CACHE], includingCACHE = 16.p11 = x16+16By theclassThe structure shows that,p11forcache_tThe first address
  3. Line 18: and p10, p11, #0x0000fffffffffffe.p10 = p11 & 00x0000fffffffffffe.0x0000fffffffffffeforbucketsThe mask of, and getP10 = first address of buckets.
  4. Line 18: p11, #0, LLookupPreopt\Function, if not 0, jump toLLookupPreopt
  5. Line 38: add p13, p10, p12, LSL #(1+PTRSHIFT).p12For the currentsel, imptheindex.p13 = buckets + (_cmd & mask) << (1 + PTRSHIFT). PTRSHIFTis3The reason why we moved 4 places to the right is to knowbucketsA memory shift requires a shift of several units. namelybuckets[i]. p13Is the current searchbucket.
  6. Line 42 ~ 44: 1:ldp p17, p9, [x13], #-BUCKET_SIZETo check whether the current location existsbucket. If found, jump toCachehit, cache hit, otherwise, loop lookup. If found, callimp.

The objc_msgSend process is as follows:

  1. throughreceiverAccess to theisa
  2. isaTo obtaincache
  3. incache(including,buckets & mask),bucketsThe mask of thebuckets.
  4. throughmaskMask to getmask(maskFor the currentbucketsthecapacipty -1).
  5. rightsel impforhashAnd get the first searchindex
  6. buckets + indexNumber of bytes in the entire cachebucket
  7. Get what you foundsel impWith the incomingsel == _cmdCompare, if equal, cache hit, callimp
  8. If you don’t want to wait,buckets + index --, look forward to jump tostep 7
  9. If the wholebucketsEnd of traversal, none found, jump toobjc_msgSend_uncached
  10. objc_msgSend_uncachedWill be updated in future articles…

supplement

Bt: Look at the stack

LLVM source address