As you can see from the previous section, the cache insert process is sent from objc_msgSend. Let’s examine the objc_msgSend process. There is some code that is not in the flow in the previous analysis, so you can look directly at the summary.

Compile analysis objc_msgSend

The demo code

        Person *p  = [Person alloc];
        [p sayHappy];
Copy the code

[p sayHappy]; Will call objc_msgSend this method, which is a message search process, the essence of the message search is through sel to find imp such a process. Objc-msg-arm64.s objC-msG-arm64.s objC-msG-arm64.s

        / / the entry
	ENTRY _objc_msgSend
	UNWIND _objc_msgSend, NoFrame
        P0 is p, the receiver of the message
        // see if p0 is null
	cmp	p0, #0			// nil check and tagged pointer check
        // If you are tagged_pointers, go LNilOrTagged
#if SUPPORT_TAGGED_POINTERS
	b.le	LNilOrTagged		// (MSB tagged pointer looks negative)
#else   / / otherwise LReturnZero
	b.eq	LReturnZero
#endif
       //x0 is the first address of the object, isa
	ldr	p13, [x0]// p13 = isa
        / / to find the class
	GetClassFromIsa_p16 p13, 1, x0	// p16 = class 
LGetIsaDone:
	// calls imp or objc_msgSend_uncached
	CacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncached
Copy the code

Let’s look at GetClassFromIsa_p16

Needs_auth =1, auth_address=isa
/ / go ExtractISA
.macro GetClassFromIsa_p16 src, needs_auth, auth_address /* note: auth_address is not required if ! needs_auth */

#if SUPPORT_INDEXED_ISA 
	// Indexed isa
	mov	p16, \src			// optimistically set dst = src
	tbz	p16, #ISA_INDEX_IS_NPI_BIT, 1f	// done if not non-pointer isa
	// isa in p16 is indexed
	adrp	x10, _objc_indexed_classes@PAGE
	add	x10, x10, _objc_indexed_classes@PAGEOFF
	ubfx	p16, p16, #ISA_INDEX_SHIFT, #ISA_INDEX_BITS  // extract index
	ldr	p16, [x10, p16, UXTP #PTRSHIFT]	// load class from array
1:

#elif __LP64__
.if \needs_auth == 0 // _cache_getImp takes an authed class already
	mov	p16, \src
.else
	// 64-bit packed isa
	ExtractISA p16, \src, \auth_address
.endif
#else
	// 32-bit raw isa
	mov	p16, \src

#endif

.endmacro

// src=isa auth_address=isa 
//isa & #ISA_MASK = $0
.macro ExtractISA
	and    $0, $1, #ISA_MASK
.endmacro

Copy the code

We’ve seen that GetClassFromIsa_p16 is a process to find a class. Why class? We know from our previous analysis that cache_t is only present in a class.

Cached NORMAL, _objc_msgSend, and __objc_msgSend_uncached

//CacheLookup NORMAL, _objc_msgSendSuper2, __objc_msgSend_uncached
//mode = NORMAL
//Function = _objc_msgSendSuper2
//MissLabelDynamic = __objc_msgSend_uncached
.macro CacheLookup Mode, Function, MissLabelDynamic, MissLabelConstant
mov	x15, x16			// stash the original isa
LLookupStart\Function:
	// p1 = SEL, p16 = isa
#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
        
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16 / / go here
	ldr	p11, [x16, #CACHE]			// p11 = mask|buckets
#if CONFIG_USE_PREOPT_CACHES
#if __has_feature(ptrauth_calls)
	tbnz	p11, #0, LLookupPreopt\Function
	and	p10, p11, #0x0000ffffffffffff	// p10 = buckets
#else / / go here
//-(cache) &0x0000FFFFFFFFFFFF
	and	p10, p11, #0x0000fffffffffffe	// p10 = buckets
	tbnz	p11, #0, LLookupPreopt\Function
#endif
      // Go here and find index
	eor	p12, p1, p1, LSR #7
	and	p12, p12, p11, LSR #48		// x12 = (_cmd ^ (_cmd >> 7)) & mask
#else
	and	p10, p11, #0x0000ffffffffffff	// p10 = buckets
	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
....
	add	p13, p10, p12, LSL #(1+PTRSHIFT)
						// p13 = buckets + ((_cmd & mask) << (1+PTRSHIFT))

						// do {
1:	ldp	p17, p9, [x13], #-BUCKET_SIZE	// {imp, sel} = *bucket--
	cmp	p9, p1				// if (sel ! = _cmd) {
	b.ne	3f				// scan more
						// } else {
2:	CacheHit \Mode				// hit: call or return imp
						/ /}
3:	cbz	p9, \MissLabelDynamic		// if (sel == 0) goto Miss;
	cmp	p13, p10			// } while (bucket >= buckets)
	b.hs	1b
Copy the code

#define CACHE_MASK_STORAGE CACHE_MASK_STORAGE_HIGH_16 下 载 arm64 下 载 LDR P11, [x16, #CACHE]

#define CACHE            (2 * __SIZEOF_POINTER__)
#define CLASS            __SIZEOF_POINTER__
Copy the code

LDR p11, [x16, #CACHE] LDR p11, [x16, 16] This is a type that finds cache_t; p11 stores cache_t

#ifdefined(__arm64__) && TARGET_OS_IOS && ! TARGET_OS_SIMULATOR && ! TARGET_OS_MACCATALYST #define CONFIG_USE_PREOPT_CACHES1
#else
#define CONFIG_USE_PREOPT_CACHES 0
#endif
Copy the code

#define CONFIG_USE_PREOPT_CACHES 1

and	p10, p11, #0x0000fffffffffffe	// p10 = buckets
tbnz	p11, #0, LLookupPreopt\Function
Copy the code

❓ p10 = buckets (cache) & 0x0000FFffFFFFFFFF (16 bits high); If the position 0 of BUCKETS P11 is not 0, LLookupPreopt is used


PTRSHIFT= 1 LSL #(1+PTRSHIFT
//(_cmd & mask) select * from bucket where index << 4
P13 is the subscript p10 is the first address of buckets array. The subscript * 1<<4(that is, 16) is used to obtain the offset of the actual memory

add	p13, p10, p12, LSL #(1+PTRSHIFT)
						// p13 = buckets + ((_cmd & mask) << (1+PTRSHIFT))

// select sel from p17 and sel from p9.
1:	ldp	p17, p9, [x13], #-BUCKET_SIZE	// {imp, sel} = *bucket--
	// Compare SEL and P1
        cmp	p9, p1				// if (sel ! = _cmd) {
	// If not, i.e., if not found, jump to 3f
        b.ne	3f				// scan more
	
        // cache hit //} else {
2:	CacheHit \Mode				// hit: call or return imp
						/ /}
3:	cbz	p9, \MissLabelDynamic		// if (sel == 0) gotoMiss;
	cmp	p13, p10                        // if (bucket >= buckets) you can jump out of the while loop and go to the bottom
        // Skip to the first step and continue the comparison
	b.hs	1b
        
        #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
        P13 points to 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
	add	p12, p10, p12, LSL #(1+PTRSHIFT)
						// p12 = first probed bucket

// start a loop from the last to the first first_probed.
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 
        
        LLookupEnd\Function:
        LLookupRecover\Function:
	b	\MissLabelDynamic 
     
Copy the code

Cached if not, we get __objc_msgSend_uncached, MissLabelDynamic, which is used when we call CacheLoopup. We get __objc_msgSend_uncached

       STATIC_ENTRY __objc_msgSend_uncached
	UNWIND __objc_msgSend_uncached, FrameWithNoSaves

	// THIS IS NOT A CALLABLE C FUNCTION
	// Out-of-band p15 is the class to search
	
	MethodTableLookup
	TailCallFunctionPointer x17

	END_ENTRY __objc_msgSend_uncached
Copy the code

Look at the TailCallFunctionPointer

.macro TailCallFunctionPointer
	// $0 = function pointer value
	br	$0
.endmacro
Copy the code

It’s just a return, so the focus should be on MethodTableLookup

.macro MethodTableLookup
	
	SAVE_REGS MSGSEND

	// lookUpImpOrForward(obj, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER)
	// receiver and selector already in x0 and x1
	mov	x2, x16
	mov	x3, #3
	bl	_lookUpImpOrForward

	// IMP in x0
	mov	x17, x0

	RESTORE_REGS MSGSEND

.endmacro
Copy the code

We see the call _lookUpImpOrForward and global search, already is not in the assembly, the next process search lookUpImpOrForward, sees in objc runtime – new. Mm

conclusion

The whole above is quite messy, we only pick out the useful ones for a summary:

ENTRY _objc_msgSend
If the receiver is not empty
// Get the class corresponding to the receiver
ldr	p13, [x0]		// p13 = isa
GetClassFromIsa_p16 p13, 1, x0	// p16 = class
// Start cache lookup
CacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncached
// Pan 16 to find cache
ldr	p11, [x16, #CACHE]			// p11 = mask|buckets
// Buckets are the last 48 digits in the cache.
and	p10, p11, #0x0000fffffffffffe	// p10 = buckets
// Select index ();
eor	p12, p1, p1, LSR #7
and	p12, p12, p11, LSR #48		// x12 = (_cmd ^ (_cmd >> 7)) & mask
// Get the corresponding bucket
add	p13, p10, p12, LSL #(1+PTRSHIFT)
						// p13 = buckets + ((_cmd & mask) << (1+PTRSHIFT))
 // The index of the index is the same as the index of the index of the index.
1:	ldp	p17, p9, [x13], #-BUCKET_SIZE	// {imp, sel} = *bucket--
	cmp	p9, p1				// if (sel ! = _cmd) {
	b.ne	3f				// scan more
						// } else {
2:	CacheHit \Mode				// hit: call or return imp
						/ /}
3:	cbz	p9, \MissLabelDynamic		// if (sel == 0) goto Miss;
	cmp	p13, p10			// } while (bucket >= buckets)
	b.hs	1b
  // If not, move p13 to the last bucket
  add	p13, p10, p11, LSR #(48 - (1+PTRSHIFT))
						// p13 = buckets + (mask << 1+PTRSHIFT)
                                                

add	p12, p10, p12, LSL #(1+PTRSHIFT)
						// p12 = first probed bucket
// From the last one forward -- traverse to firstProbed
 						// 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
        
LLookupEnd\Function:
__objc_msgSend_uncached (MethodTableLookup)
LLookupRecover\Function:
	b	\MissLabelDynamic  
 / / __objc_msgSend_uncached process
 / / go MethodTableLookup
 MethodTableLookup
 // Enter the lookUpImpOrForward method
 bl	_lookUpImpOrForward     
Copy the code

supplement

1. Looking for a bucket
// Select buckets according to the last 48 digits in the cache
and	p10, p11, #0x0000fffffffffffe	// p10 = buckets
Copy the code

_bucketsAndMaybeMask is a bucketS_t pointer in the low 48 bits & # 0x0000FFFFFFfffe

#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
    // _bucketsAndMaybeMask is a buckets_t pointer in the low 48 bits
    // _maybeMask is unused, the mask is stored in the top 16 bits.

    // How much the mask is shifted by.
    static constexpr uintptr_t maskShift = 48;

    // Additional bits after the mask which must be zero. msgSend
    // takes advantage of these additional bits to construct the value
    // `mask << 4` from `_maskAndBuckets` in a single instruction.
    static constexpr uintptr_t maskZeroBits = 4;

    // The largest mask value we can store.
    static constexpr uintptr_t maxMask = ((uintptr_t)1< < (64 - maskShift)) - 1;
    
    // The mask applied to `_maskAndBuckets` to retrieve the buckets pointer.
    static constexpr uintptr_t bucketsMask = ((uintptr_t)1 << (maskShift - maskZeroBits)) - 1;
    
    // Ensure we have enough bits for the buckets pointer.
    static_assert(bucketsMask >= MACH_VM_MAX_ADDRESS,
            "Bucket field doesn't have enough bits for arbitrary pointers.");
Copy the code
2. To find the index
/ / get the index
eor	p12, p1, p1, LSR #7
and	p12, p12, p11, LSR #48		// x12 = (_cmd ^ (_cmd >> 7)) & mask
Copy the code

Mask_t begin = cache_hash(sel, m); The algorithm to find index

static inline mask_t cache_hash(SEL sel, mask_t mask) 
{
    uintptr_t value = (uintptr_t)sel;
#if CONFIG_USE_PREOPT_CACHES
    value ^= value >> 7;
#endif
    return (mask_t)(value & mask);
}
Copy the code
3. Walk through the process
   bucket_t *b = buckets();
    mask_t m = capacity - 1; / / 4-1 = 3
    mask_t begin = cache_hash(sel, m);
    mask_t i = begin;
 do {
        if (fastpath(b[i].sel() == 0)) {
            incrementOccupied();
            b[i].set<Atomic, Encoded>(b, sel, imp, cls());
            return;
        }
        if (b[i].sel() == sel) {
            // The entry was added to the cache by some other thread
            // before we grabbed the cacheUpdateLock.
            return; }}while(fastpath((i = cache_next(i, m)) ! = begin));Copy the code

Take a look at the source code for cache_next

#elif __arm64__
static inline mask_t cache_next(mask_t i, mask_t mask) {
    return i ? i-1 : mask;
}
Copy the code

Eg: Add mask = 7, I =3 the first time, after the first call to cache_next, I = 2, then –, when I =0, I will be assigned to 7, and then move on through — until I ==3