The essence of calling methods in OC is message passing, which is done through the objc_msgSend function. So in the objc_msgSend assembler process, we end up calling a CacheLookup assembler function, which is a function that looks up cached methods.

I. Definition and parameter introduction of CacheLookup

Here’s the code to call CacheLookup in objc_msgSend:

 CacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncached
Copy the code

The CacheLookup function defines:

.macro CacheLookup Mode.Function.MissLabelDynamic.MissLabelConstant
Copy the code

In assembly, ‘.macro ‘stands for macro definition. CacheLookup is a macro definition implementation that takes four parameters: Mode, Function, MissLabelDynamic, MissLabelConstant.

  • Mode: the correspondingobjc_msgSendPassed in the callNORMAL.
  • Function: the correspondingobjc_msgSendPassed in the call_objc_msgSend.
  • MissLabelDynamic: the correspondingobjc_msgSendPassed in the call__objc_msgSend_uncached.
  • MissLabelConstant: This parameter has no default value.

Notice first that there is a comment in the code at the beginning of executing this method:

// NORMAL and LOOKUP:
// - x0 contains the receiver
// - x1 contains the selector
// - x16 contains the isa
// - other registers are set as per calling conventions
Copy the code

P0 is the sent message receiver, P1 is the sent SEL, and P16 is the sent ISA. This needs to be agreed.

Ii. CacheLookup process

1. Assembly code analysis

CacheLookup assembler code

#if CACHE_MASK_STORAGE = = CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS

// -p1 = SEL, p16 = isa -- #define CACHE (2 * __SIZEOF_POINTER__);
/ / - p10 = mask | buckets - from x16 of 16 bytes (isa) in translation, into the cache out p10
Isa (8 bytes) - superClass (8 bytes) - cache (mask high 16 bits + buckets low 48 bits)
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
Copy the code
  • This piece of code is being pulled out by memory translationcacheAnd depositp10.
  • p10Move 48 to the right and removemaskAnd assign a value top11.
  • p10 & #0xffffffffffff,bucketsAnd in thep10.

But the real machine is not go here, here posted just for the convenience of the following understanding.

2, the real machine environment to find the beginning of the cache method

The real machine goes through the following section:

// - 64-bit true
#elif CACHE_MASK_STORAGE = = CACHE_MASK_STORAGE_HIGH_16
// - p11 = mask|buckets = cache
 ldr    p11, [x16, #CACHE]            // p11 = mask|buckets
 #if CONFIG_USE_PREOPT_CACHES
     #if __has_feature(ptrauth_calls)
 tbnz    p11, #0.LLookupPreopt\Function
P11 (cache) &0x0000FFFFFFFFFFFF;
 and    p10, p11, #0x0000ffffffffffff    // p10 = buckets
     #else
 and    p10, p11, #0x0000fffffffffffe    // p10 = buckets
 tbnz    p11, #0.LLookupPreopt\Function
 #endif
 eor    p12, p1, p1, LSR #7
 and    p12, p12, p11, LSR #48        // x12 = (_cmd ^ (_cmd >> 7)) & mask
 #else
 and    p10, p11, #0x0000ffffffffffff    // p10 = buckets
// -p11 (cache) move 48 bits to the right to obtain mask (i.e. p11 storage mask), mask & p1(msgSend second parameter CMD)
// - Get sel index (search index) and store it in p12.
// -cache insert hash subscripts are computed by sel & mask, and read by sel & mask, the cache_hash function of objc-cache.mm files.
 and    p12, p1, p11, LSR #48        // x12 = _cmd & mask
Copy the code
  • Once again, take it outcachedepositp11.
  • p11 & #0x0000ffffffffffffgetbucketsAssigned top10.
  • p11Move 48 places to the right and getmask.mask & p1getThe subscriptAnd depositp12.
  • whymask & p1You get the lower index, remember thatinsertIs the hash algorithm used in the function to take the subscript, the implementation in the function is the same as this step, somask & p1You get the presentp1The correspondingbucketThe subscript.

P10 is the first address of buckets, P11 is the mask, and P12 is the subscript.

The first do while loop looks for the cache method

With buckets and buckets in hand, fetch buckets’ bucket by subscript to see if there is any SEL in the cache.

The assembly code is as follows:

// Note: LSL represents left shift, p12 is subscript, (1+PTRSHIFT) = 4. P12, LSL #(1+PTRSHIFT)
P10 + the value of the subscript moved four bits to the left is stored in P13.
// SEL and IMP take 8 bytes, so a bucket takes 16 bytes.
// In index << 4, index represents the number of buckets from buckets' first index to index. A bucket is 16 bytes if the bucket is moved 4 bits to the left.
// Buckets' index << 4 is 16 and p13 is the same as buckets' index.
 add    p13, p10, p12, LSL# (1+PTRSHIFT)
                     PTRSHIFT = buckets + ((_cmd & mask) << (1+PTRSHIFT)

// - This is the do while loop, which starts from p13 and traverses forward. If the bucket in front of p13 cannot find sel, exit the loop and continue down
                     // do {
P17 and sel to p9
1:    ldp    p17, p9, [x13], #-BUCKET_SIZE    // {imp, sel} = *bucket--
P9 (bucket sel);
 cmp    p9, p1                // if (sel ! = _cmd) {
// - If not, jump to 3f
 b.ne    3f                // scan more
                     // } else {
// - If equal, a CacheHit is hit, and imp is returned
2:    CacheHit \Mode                // hit: call or return imp
                     / /}
// - MissLabelDynamic passes __objc_msgSend_uncached when _objc_msgSend calls CacheLookup(current method).
// - So if p9 = nil, skip to __objc_msgSend_uncached.
3:    cbz    p9, \MissLabelDynamic        // if (sel == 0) goto Miss;
// check whether the value is completed or not.
 cmp    p13, p10            // } while (bucket >= buckets)
// - Continue the loop
 b.hs    1b
Copy the code
  • This code needs a lot of attention! And be sure to understandadd    p13, p10, p12, LSL #(1+PTRSHIFT)For the meaning of this line of assembly code, see the code comment.
  • The purpose of this line of code is to get what you’re looking forselThe subscriptbucketBy getting itbucketI’m going to go ahead and see if there’s anything to look forsel.
  • throughbucket >= bucketsSo it’s going to be traversalbucketsIf I find the first element and I still don’t match what I’m looking forselAnd the process continues.

4. Recalculate the search subscripts according to the environment.

The previous process has started the subscript of the SEL to be matched to search for the bucket. At this point, the bucket before the index has been searched, but not found.

Because I haven’t found all the buckets in buckets. The next step is to look for the bucket after the subscript.

The next step is to recalculate the search subscript, as shown in this assembly code:

// Calculate the subscript to start the search again, depending on the environment.
#if CACHE_MASK_STORAGE = = CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRSThis step is to recalculate buckets' subscripts so that buckets' subscripts are recalculatedCACHE_MASK_STORAGE_HIGH_16_BIG_ADDRSTake the context.Remember from the insert we explored earlier, mask is equal to capacity- 1Capacity is the size of buckets. This bit of code is like putting buckets' last bucket into P13.
 add    p13, p10, w11, UXTW# (1+PTRSHIFT)
                     // p13 = buckets + (mask << 1+PTRSHIFT)
#elif CACHE_MASK_STORAGE = = CACHE_MASK_STORAGE_HIGH_16
 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
Copy the code

This step is to recalculate buckets’ subscripts, for example, in CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS. If mask is capacity-1, capacity is the size of buckets.

This bit of code is equivalent to putting buckets’ last bucket into P13.

5. The second do while loop looks for the cache method

After recalculating the subscript, a second do while loop lookup is performed, with the following assembly code:

// Notice here, before coming here, p12 is the subscript, which is the subscript at the beginning of the first loop above, so it finds the corresponding bucket by subscript, and stores the bucket to P12. At this point, the P12 becomes the bucket above that starts the loop for the first time.
// Buckets stored in P13 are passed to buckets (sel! = 0 && bucket > first_probed) to determine whether the critical point of the first loop traversal is reached.
// If the critical point is reached, go to the next flow __objc_msgSend_uncached.
 add    p12, p10, p12, LSL# (1+PTRSHIFT)
                     // p12 = first probed bucket

Buckets = sel-IMP; // Buckets = sel-IMP; // buckets = sel-IMP; // buckets = sel-IMP
                     // do {
P17 and sel to p9
4:    ldp    p17, p9, [x13], #-BUCKET_SIZE    // {imp, sel} = *bucket--
P9 (bucket sel);
 cmp    p9, p1                // if (sel == _cmd)
// - If equal, CacheHit is hit, jump to CacheHit
 b.eq    2b                // goto hit
// - If not equal and sel not equal to nil, bucket > first_probed, continue the loop
 cmp    p9, #0                // } while (sel ! = 0 &&
 ccmp    p13, p12, #0, ne        // bucket > first_probed)
 b.hi    4b

LLookupEnd\Function:
LLookupRecover\Function:
// Skip to MissLabelDynamic (__objc_MSgsend_unached)
 b    \MissLabelDynamic
Copy the code

The process of this step is the same as that of the first do while, but the judgment conditions are changed.

Because the first time you do while, part of the method has already been looked up, to avoid repeating the lookup, pass (sel! = 0 && bucket > first_probed) to determine whether the critical point of the first cycle is traversed.

If the tipping point is reached and sel is still not matched, proceed to the next process __objc_msgSend_uncached.

CacheHit – a CacheHit

If an SEL is matched during a quick lookup of the cache method, a CacheHit is performed, which is the 2: step of assembly code.

// Call or return imp
2:    CacheHit \Mode                // hit: call or return imp
Copy the code

The compiled implementation of CacheHit is as follows:

//- Cache hit
// CacheHit: x17 = cached IMP, x10 = address of buckets, x1 = SEL, x16 = isa
.macro CacheHit
.if $0 = = NORMAL
 TailCallCachedImp 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
.endmacro
Copy the code

$0 is the Mode that was passed in, so what’s the value of Mode? The value of Mode is the Mode that was passed in by CacheLookup, and the value of Mode is NORMAL.

So calling CacheHit in CacheLookup takes the first judgment, verifying and calling IMP.

Cached methods, if not matched with SEL, go to the next step: __objc_msgSend_uncached.