preface

We’ve looked at cache_t for the method store class in previous articles (cache_t analyzes the portal). One problem we mentioned in our last article is that if the method already exists in cache_t, the next time the method is called, it will not write to cache_t. Today we’re going to look at why.

Parse method calls through Clang

The most intuitive way to view method calls is with Clang. So let’s do the following code for Person and the following code for ViewController

@interface Person : NSObject
- (void)eatFood;
- (void)goToWork;
+ (void)goToBed;
@end

@implementation Person
- (void)eatFood {
    NSLog(@"eat");
}
- (void)goToWork {
    NSLog(@"work");
}
+ (void)goToBed {
    NSLog(@"bed");
}
@end

@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    Person *person = [Person alloc];
    [person eatFood];
    [person goToWork];
    [Person goToBed];
    NSLog(@"%@--->%p", person, &person);
}
Copy the code

We use the clang command to output the.cpp file and we open the viewController.cpp file. Search the method name eatFood and find the following code:

Objc_msgSend (id)person is the receiver, sel_registerName(“eatFood”) is similar to sel

Let’s verify this by writing the following code in the source code:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        Person *person = [Person alloc];
        objc_msgSend(person, sel_registerName("eatFood"));
        objc_msgSend(person, sel_registerName("goToWork"));
        objc_msgSend(objc_getClass("Person"), sel_registerName("goToBed"));
    }
    return 0;
}
Copy the code

Above is the object method and class method that we call Person with objc_msgSend. Let’s runIf the print is correct, the underlying method call is made using objc_msgSend. Let’s take a look at how objc_msgSend finds the method and calls it.

Objc_msgSend analytical

We found objc_msgSend in the.cpp file, so the underlying implementation of objc_msgSend should be in assembly. The underlying implementation of the use of assembly benefits: 1. High efficiency, fast. 2. Type uncertainty.

A preliminary objc_msgSend

Look at the objC-MSG-arm64. s file and find ENTRY _objc_msgSend. END_ENTRY _objc_msgSend indicates the interface, that is, the _objc_msgSend method ends. We look at the whole approach

ENTRY _objc_msgSend
	UNWIND _objc_msgSend, NoFrame

	cmp	p0, #0			// nil check and tagged pointer check
#if SUPPORT_TAGGED_POINTERS
	b.le	LNilOrTagged		//  (MSB tagged pointer looks negative)
#else
	b.eq	LReturnZero
#endif
	ldr	p13, [x0]    	// p13 = isa
	GetClassFromIsa_p16 p13		// p16 = class
LGetIsaDone:
	// calls imp or objc_msgSend_uncached
	CacheLookup NORMAL, _objc_msgSend

#if SUPPORT_TAGGED_POINTERS
LNilOrTagged:
	b.eq	LReturnZero		// nil check

	// tagged
	adrp	x10, _objc_debug_taggedpointer_classes@PAGE
	add	x10, x10, _objc_debug_taggedpointer_classes@PAGEOFF
	ubfx	x11, x0, #60, #4
	ldr	x16, [x10, x11, LSL #3]
	adrp	x10, _OBJC_CLASS_$___NSUnrecognizedTaggedPointer@PAGE
	add	x10, x10, _OBJC_CLASS_$___NSUnrecognizedTaggedPointer@PAGEOFF
	cmp	x10, x16
	b.ne	LGetIsaDone

	// ext tagged
	adrp	x10, _objc_debug_taggedpointer_ext_classes@PAGE
	add	x10, x10, _objc_debug_taggedpointer_ext_classes@PAGEOFF
	ubfx	x11, x0, #52, #8
	ldr	x16, [x10, x11, LSL #3]
	b	LGetIsaDone
// SUPPORT_TAGGED_POINTERS
#endif

LReturnZero:
	// x0 is already zero
	mov	x1, #0
	movi	d0, #0
	movi	d1, #0
	movi	d2, #0
	movi	d3, #0
	ret

	END_ENTRY _objc_msgSend
Copy the code

Let’s start by analyzing where it matters

Objc_msgSend parsing

Above we have seen the entire assembly implementation of objc_msgSend. Now let’s briefly analyze the main process of objc_msgSend

  • LDR p13, [x0]–> make p13 equal to isa pointer
  • GetClassFromIsa_p16 p13–> Get the Class from isa pointer
  • LGetIsaDone:–> Obtaining the Class through ISA is complete
  • CacheLookup NORMAL, _objc_msgSend–> start CacheLookup

So at this point we’re going to cache up and CacheLookup

CacheLookup parsing

The overview method

LLookupStart$1: // p1 = SEL, p16 = isa ldr p11, [x16, #CACHE] // p11 = mask|buckets #if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16 and p10, p11, #0x0000ffffffffffff // p10 = buckets and p12, p1, p11, LSR #48 // x12 = _cmd & mask #elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4 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 p12, p10, p12, LSL #(1+PTRSHIFT) // p12 = buckets + ((_cmd & mask) << (1+PTRSHIFT)) ldp p17, p9, [x12] // {imp, sel} = *bucket 1: cmp p9, p1 // if (bucket->sel ! = _cmd) b.ne 2f // scan more CacheHit $0 // call or return imp 2: // not hit: p12 = not-hit bucket CheckMiss $0 // miss if bucket->sel == 0 cmp p12, p10 // wrap if bucket == buckets b.eq 3f ldp p17, p9, [x12, #-BUCKET_SIZE]! // {imp, sel} = *--bucket b 1b // loop 3: // wrap: p12 = first bucket, w11 = mask #if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16 add p12, p12, p11, LSR #(48 - (1+PTRSHIFT)) // p12 = buckets + (mask << 1+PTRSHIFT) #elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4 add p12, p12, p11, LSL #(1+PTRSHIFT) // p12 = buckets + (mask << 1+PTRSHIFT) #else #error Unsupported cache mask storage for ARM64. #endif // Clone scanning loop to miss instead of hang when cache is corrupt. // The slow path may detect any corruption and halt later. ldp p17, p9, [x12] // {imp, sel} = *bucket 1: cmp p9, p1 // if (bucket->sel ! = _cmd) b.ne 2f // scan more CacheHit $0 // call or return imp 2: // not hit: p12 = not-hit bucket CheckMiss $0 // miss if bucket->sel == 0 cmp p12, p10 // wrap if bucket == buckets b.eq 3f ldp p17, p9, [x12, #-BUCKET_SIZE]! // {imp, sel} = *--bucket b 1b // loop LLookupEnd$1:Copy the code

Next, we analyze the method process of CacheLookup

  • LLookupStart$1:–
  • LDR p11, [x16, #CACHE]–>p16 is now an ISA pointer to Class. Shift the p16 pointer by 16 bits to get p11. P11 is now the first bucket/mask pointer to cache_t.Since it is real, cache_t contains mixed Pointers to mask and bucket, which also contains SEL and IMP. In real machine maxMask is 1 left shift 64-48 = 16 bits then -1, value is 0-15 is 1. BucketsMask is 1 moved to the left 48-4 = 44 bits and then -1. The value is 0 minus 43 is 1
  • And p10, p11, # 0x0000FFFFFFFFFF –> 0x0000FFFFFFFFFF –> It means willMix pointer with 0x0000FFFFFFFFFFFF, here we take bucket to p10 (When we write in cache_t, buckets are 0 to 43 ones, which is similar to 0x0000FFFFFFFFFFFF and does not affect the bucket value, __ p10 The first bucket __).
  • And p12, p1, p11, LSR #48–Mask is the size of the entire cache_t), add mask to p1(sel), which is the hash algorithm for cache_t writing. P12 then gets the subscript (P12 is the subscript of the incoming SEL)
  • Add p10, p10, LSL #(1+PTRSHIFT)–>p10P12 is the passed in method. Each bucket contains sel and IMP, so each bucket is 16 bytes. Moving four bits to the left is 16 bytes to the left.)

Here is method 1

  • LDP P17, P9, [X12]–> get {IMP, SEL} = *bucket through the bucket structure
  • CMP p9, p1–> is the sel fetch and p1 passed in _cmd (that is, the sel passed in) is not equal
  • B. ne 2f–> If not equal, enter 2
  • CacheHit $0- > Imp is returned if equal

Here is the method of 2: the above lookup is not equal, and the following is a recursive lookup

  • CheckMiss $0 – – > ifIf you can't find the last element, return CheckMiss.
  • CMP p12, p10–> p10 is the first bucket, p10 is the subscript, which means (Judge subscript found not the first one)
  • B.ech3f –> If the subscript is the first, go 3
  • ldp p17, p9, [x12, #-BUCKET_SIZE]! If the bucket is not the first one, then the bucket is moved forward, and the memory is offset by -1
  • B 1b- > Go to 1

Here is the method of 3: the first bucket is found above

  • Add p12, p12, p11, LSR #(48 -(1+ PTRSHIFT))–>p11 right move 48-(1+3)=44, then hash with the first subscript p12, then hash again.The resulting subscript is the last bit of cache_t.

Then perform the 1,2 method.

As we said above, if the method is not found in the full traversal, a CheckMiss is executed. We can see from cache_t that some buckets perform two lookups during a lookup.

CheckMiss analysis

.macro CheckMiss
	// miss if bucket->sel == 0
.if $0 == GETIMP
	cbz	p9, LGetImpMiss
.elseif $0 == NORMAL
	cbz	p9, __objc_msgSend_uncached
.elseif $0 == LOOKUP
	cbz	p9, __objc_msgLookup_uncached
.else
.abort oops
.endif
.endmacro
Copy the code

This method is divided into three cases: * 1. Get imp, call LGetImpMiss * 2. If not, call _objc_msgSend_uncached * 3. Cached, _objc_msgLookup_uncached

conclusion

In this lesson, we’ll explore the underlying implementation of objc_msgSend. We’ll look at how methods are found in cache_tWe’ve just talked about how sending methods look up in cache_t,This is why, as stated at the beginning of this article, if a method is called again in cache_t, it does not go through cache_t's write method. Because the method lookup finds it returns imp directly and calls it, we no longer write to cache_t.Ached _objc_msgSend_uncached. Ached _objc_msgLookup_uncached. Ached _objc_msgSend_uncached.

The last

Finally we sort out the flow chart of Zhang objc_msgSend