Objc_msgSend to CacheLookup

  • A method is called[person say]
  • We already know that calling a method is sending a message, and the underlying call isobjc_msgSend(person, @selector(say))
  • To viewobjc_msgSendThe assembly process
  • Then throughisa&ISA_MASKfindclass
  • Then look at the jumpCacheLookupCache lookup flow, throughselLooking for aimp

CacheLookup, search sel for IMP

.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
//
// x15 = x16 = isa
mov	x15, x16			// stash the original isa x15 = x16 = isa
LLookupStart\Function: // Start the search
// 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
P11 = []
// X16 = isa
// #CACHE = 2 * __SIZEOF_POINTER__ = 16
Isa 16 gets cache_t
// p11 = [x16, #CACHE] = cache_t
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
#else
// p11 = cache_t
// 0x0000fffffffffffe = bucketsMask
// p10 = p11 & 0x0000fffffffffffe = buckets
and	p10, p11, #0x0000fffffffffffe	// p10 = buckets
// p11 ! = 0 indicates the jump
tbnz	p11, #0, LLookupPreopt\Function /
#endif
// p1 = SEL
// P12 = SEL ^ SEL >> 7
eor	p12, p1, p1, LSR #7 
// p11 = cache_t   
// mask = p11 >> 48
// p12 = p12 & mask
// p12, also known as begin index, is the location of the bucket that is initially searched
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 p12 = 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
// p12 = begin, PTRSHIFT = 3
// p12 << 4 = begin * 16 Number of bits
// p10 = buckets
// p13 = buckets + begin * 16 = buckets[begin] = bucket
P13 = buckets begin * 16 = bucket
P13 is the first bucket
add	p13, p10, p12, LSL #(1+PTRSHIFT) 
                                    // p13 = buckets + ((_cmd & mask) << (1+PTRSHIFT))

                                    // do {
// #-BUCKET_SIZE = *bucket--
// [x13] assigns bucket values to p17 and p9
// bucket {imp, sel}, 
P17 = imp, p9 = sel
1:	ldp	p17, p9, [x13], #-BUCKET_SIZE	// {imp, sel} = *bucket--
// p1 is sel of the target, i.e. say
// The sel is the same as the target SEL
cmp	p9, p1				// if (sel ! = _cmd) {
// If not, go to the bottom 3
b.ne	3f				// scan more
                                    // } else {
// Jump to CacheHit if equal cache hits
2:	CacheHit \Mode				// hit: call or return imp
                                    / /}
// Determine whether the current sel is empty, if it is empty, go Miss,
3:	cbz	p9, \MissLabelDynamic		// if (sel == 0) goto Miss;
// Compare whether the current bucket is greater than or equal to the first address until the first address is found, ending the loop
cmp	p13, p10			// } while (bucket >= buckets)
// Continue loop 1
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
// p10 = buckets
// p11 = chache_t
// mask = chache_t << 48
// PTRSHIFT = 3
// p13 = buckets + (mask << 1+PTRSHIFT)
// p13 = buckets[mask
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
// p12 = begin
// p10 = buckets
// PTRSHIFT= 3
// p12 = p10 + p12 << 4
// buckets = buckets[begin
add	p12, p10, p12, LSL #(1+PTRSHIFT)
                                    // p12 = first probed bucket

                                    // do {
// p17(imp), p9(sel), *bucket--
4:	ldp	p17, p9, [x13], #-BUCKET_SIZE	// {imp, sel} = *bucket--
// p9 = sel,
// p1 = target sel
// If sel == target sel
cmp	p9, p1				// if (sel == _cmd)
// call back to the second step above, CacheHit, for a CacheHit
b.eq	2b				// goto hit
// if sel is null
// The current bukcet is larger than the index of the first bucket
// If not, end the loop
cmp	p9, #0				// } while (sel ! = 0 &&
ccmp	p13, p12, #0, ne		// bucket > first_probed)
// Continue the loop, go 4
b.hi	4b
Copy the code
  1. throughx16 = isaTo offset#CACHE = 16,p11 = cache_t
  2. throughp11 = cache_tAnd on thebucketsMask(0x0000fffffffffffe)getp10 = buckets
  3. throughp11 = cache_tAnd on theMask (<< 48)The mask of themask
  4. Then throughp1 = SEL.p1 ^ p1 << 7 & maskThe hashing algorithm of theta gets the first onebuckettheindex (p12)
  5. Through thep10 = bucketsTranslation memoryp12 * 16In the ones place, you getp13That’s the first onebucket
  6. By judgment*bucket--, bucket>=buckets, takebucket
  7. Take out thebucketIn theSel and impwithselwithp1, for comparison
  8. Equal CacheHit
  9. If it is not, the current SEL is judged to be empty. If it is not, the cycle continues
  10. Until you find the first position and you end the loop
  11. Then position the final mask
  12. Create another loop to determine the current loop of the conditionsel ! = 0 and the bucket is larger than the initial bucket(p12)
  13. ifsel == p1, cache hit

CacheHit

// CacheHit: x17 = cached IMP, x10 = address of buckets, x1 = SEL, x16 = isa
.macro CacheHit
.if $0 == NORMAL
       / / go here
	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

.macro TailCallCachedImp
	// $0 = cached imp, $1 = address of cached imp, $2 = SEL, $3 = isa
	eor	$0, $0, $3
	br	$0
.endmacro
Copy the code
  • eor $0, $0, $3 = $0 = $0 ^ $3, that is, imp = imp ^ class
  • Because it’s encoded when you save it

  • a^b = c , c ^ b = a