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_msgSend
Is to initiate a readcache
Place.
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:
- Objective-c method calls
- NSObject provides an API
- 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 findsayNB
And so onobjc_msgSend
To achieve.
So can we just call itobjc_msgSend
Method? We may fail to compile when we callBuild Settings -> Enable Strict Checking of objc_msgSend Calls
Set 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_msgSend
When foundobjc_msgSend
The implementation is compiled as follows inarm64
In theobjc_msgSend
Assembly 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_p16
The 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:
- Line 3:
x15, x16
willisa
Stored in thex15
- Line 12:
ldr p11, [x16, #CACHE]
, includingCACHE = 16
.p11 = x16+16
By theclass
The structure shows that,p11
forcache_t
The first address - Line 18:
and p10, p11, #0x0000fffffffffffe
.p10 = p11 & 00x0000fffffffffffe
.0x0000fffffffffffe
forbuckets
The mask of, and getP10 = first address of buckets
. - Line 18:
p11, #0, LLookupPreopt\Function
, if not 0, jump toLLookupPreopt
- Line 38:
add p13, p10, p12, LSL #(1+PTRSHIFT)
.p12
For the currentsel, imp
theindex
.p13 = buckets + (_cmd & mask) << (1 + PTRSHIFT)
.PTRSHIFT
is3
The reason why we moved 4 places to the right is to knowbuckets
A memory shift requires a shift of several units. namelybuckets[i]
.p13
Is the current searchbucket
. - Line 42 ~ 44:
1:ldp p17, p9, [x13], #-BUCKET_SIZE
To 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:
- through
receiver
Access to theisa
isa
To obtaincache
- in
cache
(including,buckets & mask
),buckets
The mask of thebuckets
. - through
mask
Mask to getmask
(mask
For the currentbuckets
thecapacipty -1
). - right
sel imp
forhash
And get the first searchindex
buckets + index
Number of bytes in the entire cachebucket
- Get what you found
sel imp
With the incomingsel == _cmd
Compare, if equal, cache hit, callimp
- If you don’t want to wait,
buckets + index --
, look forward to jump tostep 7
- If the whole
buckets
End of traversal, none found, jump toobjc_msgSend_uncached
objc_msgSend_uncached
Will be updated in future articles…
supplement
Bt: Look at the stack
LLVM source address