preface

The function objc_msgSend is mentioned many times in the analysis of the cache_t principle, and the addition of the maskZeroBits field to the cache in the real world. We only know what objc_msgSend uses, but not how.

The principles of objc_msgSend and Runtime will be explored below.

Runtime

The Runtime profile

  • Building: As the name implies, while compiling, the compiler translates source code into machine-recognized code, which is really just a translation into some intermediate language. The most important thing is to do lexical analysis, syntax analysis, and check the code for compliance with the specification. This process is called compile-time type checking, also known as static type checking. The procedure code is not loaded into memory, but simply scanned as text.

  • Running: The code is running and loaded into memory. Runtime error checking is not the same as compile-time error checking (or static type checking). It is not a simple code scan, but an operation and judgment in memory.

Runtime provides a dynamically shared library of common interfaces (functions and data structures), written mostly in C, C++, and assembly, demonstrating Apple’s efforts to make dynamic systems more efficient. In our daily business, we mainly use the official Api to solve our framework needs.

The Runtime version

  • Legacy version: Earlier version, corresponding programming interface: Objective-C 1.0, 32-bit Mac OS X platform

  • Modern version: current version, corresponding programming interface: Objective-C 2.0, 64-bit systems for iPhone and Mac OS X 10.5 and later.

Runtime call Mode

There are three Runtime calls:

Objective-c code

To write and compile the objective-C source code, Runtime will complete the transition from OC to C, implementing the transformation of the underlying Runtime API, such as:

Compile to the underlying source:

Method calls are actually underlying calls to the objc_msgSend function, which will be examined in detail below.

System apis such as Framework & Service

Call the isKindOfClass method to see how it works:

Compile to the underlying source:

  • The bottom layer of NSObject’s methods is also calling objc_msgSend. Sel_registerName Gets the corresponding SEL. Objc_getClass gets an instance object.

  • It’s not just isKindOfClass: method, respondsToSelector: whether to receive a particular message; ConformsToProtocol: Specifies whether to declare the implementation protocol method. MethodForSelector: Gets the method address.

Runtime API

Direct calls to the runtime API

  • Objc_msgSend needs to import

  • The objc_msgSend configuration needs to be modified. Enable Strict Checking of objc_msgSend Calls is set to NO. The default value is YES. The purpose here is not to let the compiler check.

Print result:

A lot of objective-C code can be replaced with C implementations via the Runtime API.

Nature of method

objc_msgSend

Method is message sending, calling the underlying objc_msgSend function, for example:

Message sending: objc_msgSend(message receiver, message body (sel + parameter))

objc_msgSendSuper

Add a ZLObject subclass ZLSubObject and override doSomething as follows:

Compile to source:

  • Parent message sending: objc_msgSendSuper(parent receiver, message body (sel + parameter))

  • Subclass objects can call methods of the superclass in objc_msgSendSuper mode.

Objc_msgSendSuper is defined as follows:

If you want to simulate calling the objc_msgSendSuper function, you must understand the meaning of its method parameters. The most important structure is the super structure, which is as follows:

The current version is __OBJC2__, which ultimately simplifies to:

Call the objc_msgSendSuper function:

Print result:

The essence of the method: the process by which the message receiver finds the IMP through the SEL.

Objc_msgSend Quick lookup

Objc_msgSend assembly source code:

// objc_msgSend assembler entry
    ENTRY _objc_msgSend
    UNWIND _objc_msgSend, NoFrame
    // CMP: the comparison instruction (essentially subtraction) does not affect the value of the register.
    Objc_msgSend's first argument is (id)receiver. P0 is the receiver
    cmp	p0, #0
#if SUPPORT_TAGGED_POINTERS(__LP64__ schema execution)// le: less than or equal to 0 (p0 does not exist)
    b.le	LNilOrTagged
#else (!__LP64__ schema execution)Eq: equals 0, and the receiver does not exist.
    b.eq	LReturnZero
#endif
    // LDR: value instruction, x0 is receiver, take out isa, namely from register x0 pointed to address isa, stored in register P13
    // p13 = isa
    ldr	p13, [x0]
    P13 (isa) & ISA_MASK
    // p16 = class
    GetClassFromIsa_p16 p13, 1, x0
LGetIsaDone:
    // if there is an isa, go to the CacheLookup process, i.e. sel->imp quick lookup process
    CacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncached

#if SUPPORT_TAGGED_POINTERS(__LP64__ schema execution)LNilOrTagged:
    // Empty the register and jump out of the _objc_msgSend function
    b.eq	LReturnZero
    // Get a class from tagged pointer
    GetTaggedClass
    // Enter the cache lookup process
    b	LGetIsaDone
#endif

LReturnZero:
    // The x0 register stores 0 (receiver does not exist)
    // Clear the data
    mov	x1, #0
    movi	d0, #0
    movi	d1, #0
    movi	d2, #0
    movi	d3, #0
    // Skip the _objc_msgSend function
    ret

    END_ENTRY _objc_msgSend
Copy the code

1. Core logic:

  • Determine whether receiver is <=0.

  • If the value is <= 0, then judge the schema. If the value is __LP64__, then obtain the class through GetTaggedClass and enter the CacheLookup lookup process. Otherwise, clear the register and exit.

  • If the receiver exists, obtain the class using GetClassFromIsa_p16 and enter the CacheLookup search process.

2. Pseudocode:

void objc_msgSend(receiver, _cmd) {
    if (receiver > 0) {
        / / for CLS
        cls = GetClassFromIsa_p16(isa,1, the isa)}else {
        if (__LP64__ && receiver < = 0) {
            / / for CLS
            cls = GetTaggedClass(a); }else {
            return; }}// Quick lookup
    CacheLookup(NORMAL, _objc_msgSend,  __objc_msgSend_uncached)
}
Copy the code

GetTaggedClass

.macro GetTaggedClass
    // Target pointer: 1(tag bit) + 8(extended) + 52(data) + 3(Tag) = 64
    // x10 = isa & 111: obtains the last three tags
    and	x10, x0, #0x7
    // x11 = isa >> 55: get first 9 bits (1(tag bit) + 8(extended))
    asr	x11, x0, #55
    // Check whether x10 is equal to 7
    cmp	x10, #7
    // Ternary operation
    // If (x10 == 7)? X12 = X11 = 1(mark bit) + 8(extended)
    // Otherwise x12 = x10 = 3(tag)
    csel	x12, x11, x10, eq
    // x16 = _objc_debug_taggedpointer_classes[x12]
    // Read the base address of _objc_debug_TaggedPointer_classes into the X10 register
    adrp    x10, _objc_debug_taggedpointer_classes@PAGE
    // x10 = x10 + _objc_debug_taggedPointer_classes base offset (memory offset, get the classes array)
    add    x10, x10, _objc_debug_taggedpointer_classes@PAGEOFF
    // x16 = x10 + x12 << 3 = class
    ldr	x16, [x10, x12, LSL #3]
.endmacro
Copy the code

1. Core logic:

  • Gets the low 3 bits of the tag and the high 9 bits (flag bit +extended bit).

  • Gets the tagGedPointer memory translation

  • Class is obtained by the shift offset.

2. Pseudocode:

Class GetTaggedClass() {
    x10 = isa & 111;
    x11 = isa >> 55;
    x12 = x10 = = 7 ? x11 : x10;
    x10 = x10 << (x12 & @PAGE);
    x10 = x10 & @PAGEOFF;
    // Find CLS by offset
    cls = x16 = x10 + x12 << 3;
    return cls;
}
Copy the code

GetClassFromIsa_p16

.macro GetClassFromIsa_p16 src, needs_auth, auth_address
// armv7k or arm64_32
#if SUPPORT_INDEXED_ISA
    // Store the value of isa in register p16
    mov	p16, \src
    // TBZ: if the flag bit is 0, jump.
    // Determine that bit 0 of p16 is 0, if true, jump 1f. (ISA_INDEX_IS_NPI_BIT = 0)
    tbz	p16, #ISA_INDEX_IS_NPI_BIT, 1f
    // Read the base address of _objc_Indexed_classes into the X10 register
    adrp	x10, _objc_indexed_classes@PAGE
    // x10 = x10 + _objc_Indexed_classes base offset (memory offset, get classes array)
    add	x10, x10, _objc_indexed_classes@PAGEOFF
    // ubfx: unsigned bit field extract instruction
    // Start extraction from the second bit of p16 and extract 15 bits. Objective To extract indexCLs with the remaining bit complement of 0.
    ISA_INDEX_SHIFT = 2 and ISA_INDEX_BITS = 15 if armv7k or arm64_32
    ubfx	p16, p16, #ISA_INDEX_SHIFT#,ISA_INDEX_BITS
    // p16 = x10[indexcls & PTRSHIFT] (PTRSHIFT is 3 under __LP64__, the rest is 2)
    // p16 = class
    ldr	p16, [x10, p16, UXTP #PTRSHIFT]
1: // TBZ set up jump

#elif __LP64__
.if \needs_auth = = 0 // _cache_getImp already authorizes the class
    // Store the value of ISA in register P16 without authorization
    mov	p16, \src
.else
    // p16 = isa & ISA_MASK = class
    ExtractISA p16, \src, \auth_address
.endif
#else
    // Store the value of isa in register p16
    mov	p16, \src
#endif
.endmacro
Copy the code

1. Core logic:

The purpose of GetClassFromIsa_p16 is to get the class to p16.

  • Armv7k or arm64_32:

    Determine whether the marker bit is 0 (whether nonpointer is enabled). If it is enabled, find IndexCLs according to the offset, and obtain CLS through the and operation and assign the value to P16. Otherwise, take CLS and assign it to P16.

  • __LP64__ :

    Determine whether authorization is required (needs_auth) and if so, call ExtractISA to assign the isa & ISA_MASK result to p16; Otherwise, store the value of ISA in register P16. Needs_auth for _objc_msgSend is 1.

2. Pseudocode:

Class GetClassFromIsa_p16 (isa, needs_auth, auth_address) {
    Class cls = NULL;
    if (arm64_32 || armv7k) {
        if (nonpointer) {
            class[] = x10< < (12 & @PAGE);
            indexcls = ubfx isa[15] 2 -cls = class[indexcls]}else {
            cls =isa; }}else if (__LP64__) {
        if (needs_auth) {
            cls = ExtractISA(a); }else {
            cls =isa; }}else {
        cls = isa;
    }
    return cls;
}
Copy the code

ExtractISA assembly source:

// ISA signing authentication modes. Set ISA_SIGNING_AUTH_MODE to one
// of these to choose how ISAs are authenticated.
#define ISA_SIGNING_STRIP 1 // Strip the signature whenever reading an ISA.
#define ISA_SIGNING_AUTH  2 // Authenticate the signature on all ISAs.

// ISA signing modes. Set ISA_SIGNING_SIGN_MODE to one of these to
// choose how ISAs are signed.
#define ISA_SIGNING_SIGN_NONE       1 // Sign no ISAs.
#define ISA_SIGNING_SIGN_ONLY_SWIFT 2 // Only sign ISAs of Swift objects.
#define ISA_SIGNING_SIGN_ALL        3 // Sign all ISAs.

#if __has_feature(ptrauth_objc_isa_strips) || __has_feature(ptrauth_objc_isa_signs) || __has_feature(ptrauth_objc_isa_authenticates)
#   if __has_feature(ptrauth_objc_isa_authenticates)
#       define ISA_SIGNING_AUTH_MODE ISA_SIGNING_AUTH
#   else
#       define ISA_SIGNING_AUTH_MODE ISA_SIGNING_STRIP
#   endif
#   if __has_feature(ptrauth_objc_isa_signs)
#       define ISA_SIGNING_SIGN_MODE ISA_SIGNING_SIGN_ALL
#   else
#       define ISA_SIGNING_SIGN_MODE ISA_SIGNING_SIGN_NONE
#   endif
#else
#   if __has_feature(ptrauth_objc_isa)
#       define ISA_SIGNING_AUTH_MODE ISA_SIGNING_AUTH
#       define ISA_SIGNING_SIGN_MODE ISA_SIGNING_SIGN_ALL
#   else
#       define ISA_SIGNING_AUTH_MODE ISA_SIGNING_STRIP
#       define ISA_SIGNING_SIGN_MODE ISA_SIGNING_SIGN_NONE
#   endif
#endif

// A12 later models
#if __has_feature(ptrauth_calls)
.macro ExtractISA
    // $0 = $1 & ISA_MASK
	and	$0.The $1#,ISA_MASK
#if ISA_SIGNING_AUTH_MODE = = ISA_SIGNING_STRIP
    // Authorizes each isa read (current schema executes)
	xpacd	$0
#elif ISA_SIGNING_AUTH_MODE = = ISA_SIGNING_AUTH
    // Authorizes all ISA (does not execute)
	mov	x10, $2
    // movk: move command, movk moves the high x bit to the register, the rest of the space remains unchanged;
    // x10 = #0x6AE1 << 48 (ISA_SIGNING_DISCRIMINATOR = 0x6AE1)
	movk	x10, #ISA_SIGNING_DISCRIMINATOR.LSL #48
    // autda: authorizes backward reading x10 to P16
	autda	$0, x10
#endif
.endmacro
#else
.macro ExtractISA
    // $0 = $1 & ISA_MASK
	and    $0.The $1#,ISA_MASK
.endmacro
#endif
Copy the code

Core logic:

  • The purpose of ExtractISA is to get the class through isa & ISA_MASK.

  • A12 made the judgment of authentication logic, in which THE XPACD instruction is not very familiar.

CacheLookup assembly source code (key) :

.macro CacheLookup Mode.Function.MissLabelDynamic.MissLabelConstant

    X15 = [#x16] = [#x16]
	mov	x15, x16
// Start the search
LLookupStart\Function:
	// p1 = SEL, p16 = isa
// arm64 64位 OSX | SIMULATOR
#if CACHE_MASK_STORAGE = = CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS
    (CACHE = 2 * __SIZEOF_POINTER__ = 2 * 8 = 16 bits, __SIZEOF_POINTER__ is a pointer, 8 bits)
    // p10 = isa & #CACHE = _bucketsAndMaybeMask(cache_t)
	ldr	p10, [x16, #CACHE]
    // LSR: logical right shift
    // p11 = p10(_bucketsAndMaybeMask) >> 48 = mask. (access mask)
	lsr	p11, p10, #48
    P10 = p10(_bucketsAndMaybeMask) & 0xFFFFFFFFFFFF (12 bits) = buckets. (Only retain 48 bits to get buckets)
	and	p10, p10, #0xffffffffffff
    // w1: second argument SEL, w11 = p11 = mask (execute cache_hash() to get insert begin)
    // p12 = SEL & mask = Insert begin
	and	w12, w1, w11
// Arm64 64-bit real machine
#elif CACHE_MASK_STORAGE = = CACHE_MASK_STORAGE_HIGH_16
    // p11 = isa & #CACHE = _bucketsAndMaybeMask(cache_t)
	ldr	p11, [x16, #CACHE]
// arm64 IOS && ! Simulator &&! MAC app
#if CONFIG_USE_PREOPT_CACHES
/ / A12 real machine
#if __has_feature(ptrauth_calls)
    // TBNZ: jump if the flag bit is not 0 (as opposed to TBZ).
    // P11 If bit 0 is not 0, jump to LLookupPreopt
	tbnz	p11, #0.LLookupPreopt\Function
    // p10 = p11(_bucketsAndMaybeMask) & 0x0000ffffffffffff = buckets
	and	p10, p11, #0x0000ffffffffffff
// Other real machines
#else
    // p10 = p11(_bucketsAndMaybeMask) & 0x0000fffffffffffe = buckets
	and	p10, p11, #0x0000fffffffffffe
    // P11 If bit 0 is not 0, jump to LLookupPreopt
	tbnz	p11, #0.LLookupPreopt\Function
#endif // __has_feature(ptrauth_calls)
    // eOR: xOR directive
    // p12 = sel ^ (sel >>7) (execute the cache_hash() function and perform the >>7 operation on the real machine)
	eor	p12, p1, p1, LSR #7
    // p11(_bucketsAndMaybeMask) >> 48 = mask
    // p12 = (sel ^ (sel >> 7)) & mask
	and	p12, p12, p11, LSR #48
#else
    // p10 = p11(_bucketsAndMaybeMask) & 0x0000ffffffffffff = buckets
	and	p10, p11, #0x0000ffffffffffff
    // p11(_bucketsAndMaybeMask) >> 48 = mask
    // p12 = sel & mask
	and	p12, p1, p11, LSR #48
#endif // CONFIG_USE_PREOPT_CACHES
/ / 64
#elif CACHE_MASK_STORAGE = = CACHE_MASK_STORAGE_LOW_4
    // p11 = isa & #CACHE = _bucketsAndMaybeMask(cache_t)
	ldr	p11, [x16, #CACHE]
    P10 = p11&~ 0xf = buckets(first 28); // Buckets = p11&~ 0xf = buckets
	and	p10, p11, #~0xf
    // p11 = p11&0xf = maskShift(last 4 bits)
	and	p11, p11, #0xf
    // p12 = 0xffff
	mov	p12, #0xffff
    // p11 = p12 >> p11 = 0xffff >> maskShift = mask
	lsr	p11, p12, p11
    // p12 = sel & mask
	and	p12, p1, p11
#else
#error Unsupported cache mask storage for ARM64.
#endif
    
    // p10 = buckets; p12 = sel & mask; PTRSHIFT = 3 for 64-bit and PTRSHIFT = 2 for others
    // p13 = buckets & ((sel & mask) << (1 + PTRSHIFT)) = bucket
	add	p13, p10, p12, LSL# (1+PTRSHIFT)
    / / {p17 (imp), p9 (sel)} = * p13 [BUCKET_SIZE -] (memory translation)
1:	ldp	p17, p9, [x13], #-BUCKET_SIZE
    P9 == p1?
	cmp	p9, p1
    // If not (not found), jump to step 3
	b.ne	3f

    // If a CacheHit is found, perform CacheHit
2:	CacheHit \Mode
    
    // CBZ: indicates 0.
    // Sel == 0, if true, jump MissLabelDynamic
3:	cbz	p9, \MissLabelDynamic
    // buckets >= buckets
	cmp	p13, p10
    // If the buckets are greater than or equal to (bucket >= buckets), go to step 1
	b.hs	1b

// If no bucket is matched, look for the element corresponding to p13 = mask
// p10 = buckets; p11 = mask;
#if CACHE_MASK_STORAGE = = CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS
    // UXTW: extended instruction to expand w11 to 64 bits by 1+PTRSHIFT
    // p13 = buckets & (mask << 1+PTRSHIFT) = bucket
	add	p13, p10, w11, UXTW# (1+PTRSHIFT)
				
#elif CACHE_MASK_STORAGE = = CACHE_MASK_STORAGE_HIGH_16
    // maskZeroBits bits are ignored here
    // p13 = buckets & (mask >> (48 - (1+PTRSHIFT)) = bucket
	add	p13, p10, p11, LSR# (48 - (1+PTRSHIFT))

#elif CACHE_MASK_STORAGE = = CACHE_MASK_STORAGE_LOW_4
    // p13 = buckets & (mask << 1+PTRSHIFT) = bucket
	add	p13, p10, p11, LSL# (1+PTRSHIFT)

#else
#error Unsupported cache mask storage for ARM64.
#endif
    // p12 = sel & mask
    // p12 = buckets & (sel & mask << (1+PTRSHIFT)) = first_probed bucket
	add	p12, p10, p12, LSL# (1+PTRSHIFT)

    / / {p17 (imp), p9 (sel)} = * p13 [BUCKET_SIZE -] (memory translation)
4:	ldp	p17, p9, [x13], #-BUCKET_SIZE
    P9 (sel) = _cmd
	cmp	p9, p1
    // If so, jump to Step 2 CacheHit
	b.eq	2b
    // If not, determine whether p9(sel) is equal to 0
	cmp	p9, #0
    P13 (bucket) > p12(first_probed bucket)
	ccmp	p13, p12, #0, ne
    // Hi: unsigned greater than, repeat step 4
	b.hi	4b

LLookupEnd\Function:
LLookupRecover\Function:
	b	\MissLabelDynamic
Copy the code

1. Core logic:

  • Overall process: CacheLookup → Buckets → bucket → SEL & IMP

  • Identify the architecture and find buckets and the index of sel in buckets. P10 = buckets, p11 = mask, p12 = index.

  • If bit 0 of _bucketsAndMaybeMask is not 0 in arm64 64-bit, run LLookupPreopt().

  • Two cyclic lookup processes:

    • First, buckets & index << 4 is used to find the corresponding bucket. The do-while loop determines the sel [index]. If the sel is found, the CacheHit is executed. If sel is empty, it means there is no cache, and __objc_msgSend_uncached().

    • The element corresponding to p13 = buckets[mask] is the second to last. P13 = buckets & mask << 4 Locate the bucket corresponding to the mask, and the do-while loop determines the SEL of buckets[mask] up to index. If it is hit, perform CacheHit. If there is an empty SEL, then there is no cache and the loop is closed.

  • __objc_msgSend_uncached()

  • Has always been true machine environment, maskZeroBits 4 0 (0 x0000ffffffffffff | 0 x0000fffffffffffe), access to the bucket, After _bucketsAndMaybeMask >> 44, you do not need to <<4 to find the address of the corresponding bucket.

  • A) front B) back C) front D) back

2. Pseudocode:

// CacheLookup(NORMAL, _objc_msgSend, __objc_msgSend_uncached, NULL)
void CacheLookup(Mode.Function.MissLabelDynamic.MissLabelConstant) {
    x15 = x16;
    if (__LP64__ && (TARGET_OS_OSX || TARGET_OS_SIMULATOR)) {
        _bucketsAndMaybeMask = isa & 16;
        mask = _bucketsAndMaybeMask >> 48;
        buckets = _bucketsAndMaybeMask & 0xffffffffffff;
        index = sel & mask;
    } else if (__LP64__) {
        // _bucketsAndMaybeMask
        _bucketsAndMaybeMask = isa & 16;
        if (IOS && !SIMULATOR && !MACCATALYST) {
            // A12
            if (__has_feature(ptrauth_calls)) {
                // Determine the flag bit
                if(_bucketsAndMaybeMask [the first0A]! = 0) {
                    LLookupPreopt(a); }else {
                    buckets = _bucketsAndMaybeMask & 0x0000ffffffffffff}}else {
                buckets = _bucketsAndMaybeMask & 0x0000fffffffffffe;
                // Determine the flag bit
                if(_bucketsAndMaybeMask [the first0A]! = 0) {
                    LLookupPreopt();
                }
            }
            / / calculate the index
            mask = _bucketsAndMaybeMask >> 48;
            index = sel ^ (sel >> 7) & mask;
        } else {
            buckets = _bucketsAndMaybeMask & 0x0000ffffffffffff;
            mask = _bucketsAndMaybeMask >> 48;
            / / calculate the index
            index = sel &mask; }}else if (!__LP64__) {
        _bucketsAndMaybeMask = isa & 16;
        buckets = _bucketsAndMaybeMask & ~0xf;
        maskShift = _bucketsAndMaybeMask & 0xf;
        mask = 0xffff >> maskShift;
        index = sel & mask;
    }
    // Get the target
    bucket = buckets & (index << 4);
    i = index;
    / / loop
    do {
        bucket = buckets[i--];
        if (bucket.sel() = = _cmd) {
            / / hit
            CacheHit(NORMAL);
            return;
        }
        if (bucket.sel() = = 0) {
            // __objc_msgSend_uncached()
            MissLabelDynamic(a);return; }}while(bucket > = buckets);
    
    // If no cache is found
    if (__LP64__ && (TARGET_OS_OSX || TARGET_OS_SIMULATOR)) {
        bucket = buckets & (mask << 4);
    } else if (__LP64__) {
        bucket = buckets & (mask >> 44);
    } else if (!__LP64__) {
        bucket = buckets & (mask << 3);
    }
    index = sel & mask;
    first_probed = buckets & (index << 4);
    i = index;
    / / loop
    do {
        bucket = buckets[i--];
        if (bucket.sel() = = _cmd) {
            / / hit
            CacheHit(NORMAL);
            return; }}while(bucket.sel() ! = 0 && bucket > first_probed);
    // The cache is still not found. The cache does not exist.
    __objc_msgSend_uncached()
}

Copy the code

LLookupPreopt assembly source:

LLookupPreopt\Function:
/ / A12 models
#if __has_feature(ptrauth_calls)
    // p10 = _bucketsAndMaybeMask & 0x007ffffffffffffe = buckets
	and	p10, p11, #0x007ffffffffffffe
    // Authorize to read forward x10 = CLS
	autdb	x10, x16
#endif
    // p9 = _cmd
	adrp	x9, _MagicSelRef@PAGE
    // _cmd = (_cmd >> 12 + @PAGE) << 12 + PAGEOFF
	ldr	p9, [x9, _MagicSelRef@PAGEOFF]
    // p12 = _cmd - p9 = index
	sub	p12, p1, p9

	// w9 = ((_cmd - first_shared_cache_sel) >> hash_shift & hash_mask)
#if __has_feature(ptrauth_calls)
	// bits 63.. 60 of x11 are the number of bits in hash_mask
	// bits 59.. 55 of x11 is hash_shift
    // p17 = _bucketsAndMaybeMask >> 55 = hash_shift
	lsr	x17, x11, #55
    // p9 = index >> hash_shift
	lsr	w9, w12, w17
    // p17 = _bucketsAndMaybeMask >> 60 = mask_bits = 4
	lsr	x17, x11, #60
    // p11 = #0x7fff
	mov	x11, #0x7fff
    // p11 = #0x7fff >> mask_bits = mask
	lsr	x11, x11, x17
    // p9 = p9 & mask
	and	x9, x9, x11
#else
	// bits 63.. 53 of x11 is hash_mask
	// bits 52.. 48 of x11 is hash_shift
    // p17 = _bucketsAndMaybeMask >> 48 = hash_shift
	lsr	x17, x11, #48			// w17 = (hash_shift, hash_mask)
    // p9 = index >> hash_shift
	lsr	w9, w12, w17
    // p11(_bucketsAndMaybeMask) >> 53 = mask
    // p9 = p9 & mask
	and	x9, x9, x11, LSR #53
#endif
    
    // x17 == sel_offs | (imp_offs << 32)
	ldr	x17, [x10, x9, LSL #3]
    // Compare index to p17
	cmp	x12, w17, uxtw

.if \Mode = = GETIMP
    // If not, cache miss
	b.ne	\MissLabelConstant
    // p0 = isa - p17 >> 32 = imp
	sub	x0, x16, x17, LSR #32
    // Register IMP to P0 and return
	SignAsImp x0
	ret
.else
    // If not, go to 5
	b.ne	5f
    // p17 = isa - p17 >> 32 = imp
	sub	x17, x16, x17, LSR #32
.if \Mode = = NORMAL
    // Jump to register P17
	br	x17
.elseif \Mode = = LOOKUP
    // p16 = p16 | 0x11
	orr x16, x16, #3
    // Register imp to P17 and return
	SignAsImp x17
	ret
.else
.abort  unhandled mode \Mode
.endif

    // p9 = *buckets--
5:	ldursw	x9, [x10, #-8]
    // x16 = x16 & x9
	add	x16, x16, x9
    / / jump LLookupStart
	b	LLookupStart\Function
.endif
#endif // CONFIG_USE_PREOPT_CACHES

.endmacro
Copy the code

1. Core logic:

  • The purpose of LLookupPreopt is to obtain imp and return.

  • If not, do another cache lookup.

2. Pseudocode:

void LLookupPreopt() {
    if (__has_feature(ptrauth_calls)) {
        buckets = _bucketsAndMaybeMask & 0x007ffffffffffffe;
    }
    // Calculate the difference index
    index = (_cmd - first_shared_cache_sel);
    if (__has_feature(ptrauth_calls)) {
        hash_shift = _bucketsAndMaybeMask >> 55;
        mask_bits = _bucketsAndMaybeMask >> 60;
        mask = 0x7fff >> mask_bits;
        _cmd = (index >> hash_shift) && mask;
    } else {
        hash_shift = _bucketsAndMaybeMask >> 48;
        mask = _bucketsAndMaybeMask >> 53;
        _cmd = (index >> hash_shift) && mask;
    }
    // Judge mode
    if (Mode = = GETIMP) {
        if (index = = sel_offs) {
            imp = isa - p17 >> 32;
            return imp;
        } else {
            return 0; }}else {
        if (index = = sel_offs) {
            imp = isa - p17 >> 32;
            if (Mode = = NORMAL) {
                return imp;
            } else if (Mode = = LOOKUP) {
                isa = isa | 0x11;
                returnimp; }}else {
            isa = isa & *buckets--;
            LLookupStart(a); }}}Copy the code

CacheHit assembler source:

#define NORMAL 0
#define GETIMP 1
#define LOOKUP 2

// A12 later models
#if __has_feature(ptrauth_calls)
.macro TailCallCachedImp
    // $0 = cached imp, $1 = address of cached imp, $2 = SEL, $3 = isa
    // $1 = buckets ^ SEL;
    eor    The $1.The $1.$2    // mix SEL into ptrauth modifier
    // $1 = (buckets ^ SEL) ^ isa;
    eor    The $1.The $1.$3  // mix isa into ptrauth modifier
    // $0(p17) = (buckets ^ SEL) ^ isa;
    brab    $0.The $1
.endmacro

.macro AuthAndResignAsIMP
    // $0 = cached imp, $1 = address of cached imp, $2 = SEL, $3 = isa
    // $1 = buckets ^ SEL;
    eor    The $1.The $1.$2    // mix SEL into ptrauth modifier
    // $1 = (buckets ^ SEL) ^ isa;
    eor    The $1.The $1.$3  // mix isa into ptrauth modifier
    $0 = $1
    autib    $0.The $1
    // xar: zero register
    // Read the $0 to zero register. If the read is successful, the validation failed
    ldr    xzr, [$0]
    // paciza: indicates the paciza signature for the address
    paciza    $0
.endmacro
#else
.macro TailCallCachedImp
    // $0 = cached imp, $1 = address of cached imp, $2 = SEL, $3 = isa
    // $0 = imp ^ isa
    eor    $0.$0.$3
    br    $0
.endmacro

.macro AuthAndResignAsIMP
    // $0 = cached imp, $1 = address of cached imp, $2 = SEL, $3 = isa
    // $0 = imp ^ isa
    eor    $0.$0.$3
.endmacro
#endif

// CacheHit: x17 = cached IMP, x10 = address of buckets, x1 = SEL, x16 = isa
.macro CacheHit
.if $0 = = NORMAL
    // Verify and log imp
    // TailCallCachedImp(imp, buckets, sel, isa)
	TailCallCachedImp x17, x10, x1, x16
.elseif $0 = = GETIMP
	mov	p0, p17
    // Compare p0 to 0. If p0 is 0, jump to 9
	cbz	p0, 9f
    // If not 0, verify that the signature is IMP
	AuthAndResignAsIMP x0, x10, x1, x16
// return IMP
9:	ret
.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.
    // Authorizes imp signature
	AuthAndResignAsIMP x17, x10, x1, x16
    // Compare p16 with p15, which was assigned when _objc_msgSend was started.
	cmp	x16, x15
    / / when the p15! = p16, p16++;
	cinc	x16, x16, ne
    // return imp
	ret
.else
.abort oops
.endif
.endmacro
Copy the code

1. Core logic:

  • The _objc_msgSend executes NORMAL logic, executes TailCallCachedImp directly, and its internal execution (BUCKETS ^ SEL) ^ ISA, decodes imp, and returns IMP.

  • Both GETIMP and LOOKUP authorize imp registration and return imp. The difference is that GETIMP is a judgment after the imp authorization; LOOKUP first authorized IMP to judge.

2. Pseudocode:

IMP CacheHit(mode) {
    if (mode = = NORMAL) {
        return TailCallCachedImp(imp, buckets, sel, isa);
    } else if (mode = = GETIMP) {
        if (imp = = 0) {
            return imp;
        } else {
            return AuthAndResignAsIMP(imp, buckets, sel, isa); }}else if (mode = = LOOKUP) {
        imp = AuthAndResignAsIMP(imp, buckets, sel, isa);
        if (curCls ! = firstCls) {
            curCls++;
        }
        return imp;
    } else {
        return 0; }}IMP TailCallCachedImp(imp, buckets, sel, isa) {
    if (__has_feature(ptrauth_calls)) {
        imp = buckets ^ sel;
        imp = imp ^ isa;
        return imp;
    } else {
        imp = imp ^ isa;
        returnimp; }}IMP AuthAndResignAsIMP(imp, buckets, sel, isa) {
    if (__has_feature(ptrauth_calls)) {
        imp = buckets ^ sel;
        imp = imp ^ isa;
        if (imp = = 0) {
            paciza imp;
        }
        return imp;
    } else {
        imp = imp ^ isa;
        returnimp; }}Copy the code

__objc_msgSend_uncached

The __objc_msgSend_uncached function is executed if no cache is hit.

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
// Call MethodTableLookup (which involves slow lookup logic)
MethodTableLookup
/ / returns the imp
TailCallFunctionPointer x17
END_ENTRY __objc_msgSend_uncached
Copy the code

Mainly execute MethodTableLookup, which involves slow lookups. The next chapter will be analyzed.

Objc_msgSend Finds a flowchart quickly