The previous objc_msgSend assembly analysis got the class and then started looking for the cache analysis
CacheLookup compiled and analyzed
//CacheLookup LOOKUP, _objc_msgLookup, __objc_msgLookup_uncached
.macro CacheLookup Mode, Function, MissLabelDynamic, MissLabelConstant
//
// Restart protocol:
//
// As soon as we're past the LLookupStart\Function label we may have
// loaded an invalid cache pointer or mask.
//
// When task_restartable_ranges_synchronize() is called,
// (or when a signal hits us) before we're past LLookupEnd\Function,
// then our PC will be reset to LLookupRecover\Function which forcefully
// jumps to the cache-miss codepath which have the following
// requirements:
//
// GETIMP:
// The cache-miss is just returning NULL (setting x0 to 0)
//
// NORMAL and LOOKUP:
// - x0 contains the receiver
// - x1 contains the selector
// - x16 contains the isa
// - other registers are set as per calling conventions
//
// Move class to x15
mov x15, x16 // stash the original isa
LLookupStart\Function:
// p1 = SEL, p16 = isa
//TARGET_OS_OSX/TARGET_OS_SIMULATOR
#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
/ / machine arm64 really
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
//#define CACHE (2 * __SIZEOF_POINTER__)
//[x16, #CACHE] translates x16 (isa) CACHE size to cache_t
// LDR p11, cache_t Places cache_t on p11, that is, p11 = cache_t
ldr p11, [x16, #CACHE] // p11 = mask|buckets
//CONFIG_USE_PREOPT_CACHES: arm64&&ios && not emulator && not MAC
#if CONFIG_USE_PREOPT_CACHES
//iphoneX and later devices
#if __has_feature(ptrauth_calls)
// Compare p11 with 0, if not, LLookupPreopt\Function
tbnz p11, #0, LLookupPreopt\Function
P11 (cache_t) starts at 0x0000FFFFFFFFFF and gives buckets to P10
and p10, p11, #0x0000ffffffffffff // p10 = buckets
#else
P11 (cache_t) starts with buckets &0x0000FFfffffffe
and p10, p11, #0x0000fffffffffffe // p10 = buckets
// Compare p11 with 0, if not, LLookupPreopt\Function
tbnz p11, #0, LLookupPreopt\Function
#endif
// p11 = 0
// P0 is receiver, p1 is sel
// eOR logic xor (^)
// Move P1 (sel) 7 positions to the right after ^ SEL, give it to p12
eor p12, p1, p1, LSR #7
// the first address of p11 (cache_t) is moved 48 bits to the right & p12 to p12
and p12, p12, p11, LSR #48 // x12 = (_cmd ^ (_cmd >> 7)) & mask
#else
and p10, p11, #0x0000ffffffffffff // p10 = buckets
// p11, LSR #48 p11 (cache_t
//p1(sel) & mask get index to 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
// #define PTRSHIFT 3
// P12 (index) moves to the left (PTRSHIFT+1)
//add p13, p10, p12: buckets + left (p13
add p13, p10, p12, LSL #(1+PTRSHIFT)
// p13 = buckets + ((_cmd & mask) << (1+PTRSHIFT))
// do {
// Put *bucket-- the contents in p17 and p9 respectively
Sel (p9) imp(p17) sel(p9)
1: ldp p17, p9, [x13], #-BUCKET_SIZE // {imp, sel} = *bucket--
If they are the same, they will go to 2. If they are not, they will go to 3f. In fact, it is a circular search
cmp p9, p1 // if (sel ! = _cmd) {
b.ne 3f // scan more
// } else {
// Cache hit Mode is passed NORMAL
2: CacheHit \Mode // hit: call or return imp
/ /}
/ / p9 is empty
3: cbz p9, \MissLabelDynamic // if (sel == 0) goto Miss;
If p13 is greater than or equal to p10 (buckets) then bucket--
cmp p13, p10 // } while (bucket >= buckets)
b.hs 1b
// wrap-around:
// p10 = first bucket
// p11 = mask (and maybe other bits on LP64)
// p12 = _cmd & mask
//
// A full cache can happen with CACHE_ALLOW_FULL_UTILIZATION.
// So stop when we circle back to the first probed bucket
// rather than when hitting the first bucket again.
//
// Note that we might probe the initial bucket twice
// when the first probed slot is the last entry.
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS
add p13, p10, w11, UXTW #(1+PTRSHIFT)
// p13 = buckets + (mask << 1+PTRSHIFT)
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
// Locate the last bucket
add p13, p10, p11, LSR #(48 - (1+PTRSHIFT))
// p13 = buckets + (mask << 1+PTRSHIFT)
// see comment about maskZeroBits
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
add p13, p10, p11, LSL #(1+PTRSHIFT)
// p13 = buckets + (mask << 1+PTRSHIFT)
#else
#error Unsupported cache mask storage for ARM64.
#endif
// The shared cache may also be stored for P12
add p12, p10, p12, LSL #(1+PTRSHIFT)
// p12 = first probed bucket
// do {
4: ldp p17, p9, [x13], #-BUCKET_SIZE // {imp, sel} = *bucket--
cmp p9, p1 // if (sel == _cmd)
b.eq 2b // goto hit
cmp p9, #0 // } while (sel ! = 0 &&
ccmp p13, p12, #0, ne // bucket > first_probed)
b.hi 4b
Copy the code
// CacheHit: x17 = cached IMP, x10 = address of buckets, x1 = SEL, x16 = isa
.macro CacheHit
.if $0 == NORMAL
如果 $0TailCallCachedImp 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
Copy the code
.macro TailCallCachedImp
// $0 = cached imp, $1 = address of cached imp, $2 = SEL, $3 = isa
// x17 = cached IMP, x10 = address of buckets, x1 = SEL, x16 = isa
// eOR logic xor (^)
// $0^$3 = x17 ^ x16 = IMP ^ class IMP coding
eor $0, $0, $3
/ / call the IMP
br $0
Copy the code
The logic of CacheLookup looks something like this:
- will
isa
First address translation16
In bytes, you getcache_t
In theobjc_class
In the structure,cache
The front andisa
andsuperclass
Each have8
Bytes. - will
cache_t
The first address of the0x0000fffffffffffe
getbuckets
- will
cache_t
with0
Compare, if not0
Is executedLLookupPreopt\Function
, if is0
, the implementation of the first4
step - will
_objc_msgSend
The second argument passed inSEL
And on themask
(cache
Moves to the right48
Who getmask
)bucket
The subscriptindex
buckets
The first address+
Offset (subscriptindex
Shift to the left (PTRSHIFT
+ 1),PTRSHIFT
=3
) take outbucket
do-while
Cycle, whenbucket
The address is less thanbuckets
The loop ends at the first address. Loop the following operation:
-
6.1 Decrease the address by BUCKET_SIZE each time
-
6.2 Check whether the SEL in the bucket is the same as the SEL sent by objc_msgSend
-
6.3 if they are the same, a CacheHit will occur. Execute CacheHit, IMP = IMP^ISA to obtain the IMP and invoke it
-
6.4 If they are different, judge whether SEL extracted from bucket is empty. If it is empty, execute MissLabelDynamic; otherwise, decrease bucket address each time according to BUCKET_SIZE and search forward.
-
__objc_msgSend_uncached if not found at the end of the loop
Objc_msgSend and CacheLookup flowcharts
Objc_msgSend and CacheLookup flowchart summary
The objc_msgSend process is to get the IMP through sel and call imp. The process is as follows:
- judge
recevier
If there is a - through
recevier
theisa
getclass
class
Memory translation fetchcache
, which containsbucket
andmask
cache
The first address is up0x0000fffffffffffe
getbuckets
cache
Move the head address to the right48
Who getmask
. Need to bemask
becauseinsert
The hash function is computed as follows:(mask_t)(value & mask)
- Get the first lookup
index
.index = SEL&mask
. bucket+index
Number one in the entire cachebucket
- Take out the
bucket
In thesel
sel
withobjc_msgSend
The incomingsel
Compare, equal hit cache, callIMP
- Unequal let it be
bucket--
To return to the first7
Step start cycle - Execute if the end of the loop is not found
__objc_msgSend_uncached
supplement
This is the first red boxvalue
is0
When a method is called[p saySomething]
(Second red box),value
is7
According to the previous pairCache_t
theexplore, the initial capacity is4
, when the capacity is greater than the current volumeThree quarters of
, will be expanded. So when you callsaySomething
When, what other methods occupy the capacity?
In cache_t:: INSERT method unsigned oldCapacity = capacity(), capacity = oldCapacity; Insert the following code after:
if (sel == @selector(saySomething)) {
bucket_t *kc_b = buckets(a);for (unsigned i = 0; i<oldCapacity; i++) {
SEL kc_sel = kc_b[i].sel(a); IMP kc_imp = kc_b[i].imp(kc_b,nil);
printf("%p - %p - %p\n",kc_sel,kc_imp,&kc_b[i]);
}
printf("isConstantEmptyCahe %p - %u - %u -%u\n",kc_b,capacity,newOccupied,oldCapacity);
}
Copy the code
The purpose of inserting this code is to callsaySomething
Method, prints out the current capacity using method.lldb
The debugging result is as follows:The first two arerespondsToSelector:
andclass
The third one is empty. The fourthimp
Address and the first onebucket
It’s the same address both of them0x10144d050
. inallocateBuckets
Function,That means an extra one will be added at the end of the listbucket
.sel
for1
And point to the first onebucket
.
So back to the question, why is value 7?
Because there are currently four methods, which are larger than 3/4 of the current capacity (default 4), the capacity has been expanded.