This is the 7th day of my participation in the August More Text Challenge

First, runtime runtime

Compile time and run time

  • Compile timeWhat is compiling? A compiler helps you translate the source code into code that the machine can recognize. Compile time checks code, lexical analysis, syntax analysis. This process of checking is often called static type checking
  • The runtimeThe code runs and is loaded into memory. Runtime error checking is not the same as compile-time error checking. It is not a simple code scan, but rather an in-memory operation and judgment

2. Runtime version

There are two versions of Runtime: Legacy and Modern.

  • Programming interfaces for earlier versions:Objective - 1.0 C

    Earlier versions were used forObjective - 1.0 C, 32 bitsMac OS XThe platform of
  • Programming interface of current version:Objective - 2.0 C, often seen in the source codeOBJC2

    Current version used forObjective - 2.0 C, iPhone apps andMac OS X v10.5And 64-bit programs in later systems

3. How the Runtime is initiated

The Runtime can be initiated in three ways:

  • throughOC method.[obj method]
  • throughNSObjectThe method ofisKindofClass performSelector
  • throughObjc underlyingTo provide theapi.class_getInstanceSize objc_msgSend

4. Call the underlying analysis method

Look at the code below to create an object and call its two methods

LRPerson *person = [LRPerson alloc];
[person saySomething];
[person sayHello:@"Hello"];
Copy the code

Use clang to compile code into C++ code

LRPerson *person = ((LRPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("LRPerson"), sel_registerName("alloc"));
((void (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("saySomething"));
((void (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("sayHello:"), (NSString *)&__NSConstantStringImpl__var_folders_tc_3wwljjy118gc8b_00djjc3sh0000gn_T_main_4057b5_mi_2);
Copy the code

You can see that underneath, methods are called through the objc_msgSend function

So the essence of a method call is message sending: objc_msgSend(receiver of message, body of message (sel + argument))

5, objc_msgSend

Now that the method call is sending the message through objc_msgSend. Is it possible to call the method directly in the code through objc_msgSend?

Call before the first inBuild SettingLt.Enable Strict Checking of objc_msgSend CallsSet toNOTo turn off the compiler check

#import <objc/message.h>

LRPerson *person = [LRPerson alloc];
[person saySomething];
objc_msgSend(person, @selector(saySomething));
[person sayHello:@"Hello"];
Copy the code

After trying to print the same results, you can call the method directly with objc_msgSend

Objc_msgSend low-level assembly analysis

1, find assembly source code

At the call method interrupt point go into assembly and findobjc_msgSendJust follow in and you’ll find itobjc_msgSendfromobjcThe underlying source

Look in the underlying source codeobjc_msgSendDirectly, seearm64Real machine architecture, inobjc_msg_arm64.sView the underlying source code, findENTRYThe entranceFirst, look at the macro definition used in the source code.

2. Source code analysis

    ENTRY _objc_msgSend _objc_msgSend(id receiver, SEL _cmd)
    UNWIND _objc_msgSend, NoFrame
    
    // CMP: p0 is compared with 0. P0 is the first parameter.
    cmp p0, #0          // nil check and tagged pointer check
#if SUPPORT_TAGGED_POINTERS  // For 64-bit systems, the TaggedPointers type is supported
    b.le    LNilOrTagged        // (MSB tagged pointer looks negative) <=0 jump LNilOrTagged
#else
    b.eq    LReturnZero  // ==0 The message is empty jump LReturnZero
#endif
    ldr p13, [x0]       // p13 = isa; // P13 = isa
    GetClassFromIsa_p16 p13, 1, x0  // p16 = class GetClassFromIsa_p16(ISA, 1, receiver)
LGetIsaDone: // After obtaining isa, proceed with the following process
    // calls IMP and objc_msgSend_uncached (NORMAL,_objc_msgSend,__objc_msgSend_uncached)
    CacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncached

#if SUPPORT_TAGGED_POINTERS
LNilOrTagged:
    b.eq    LReturnZero     // nil check
    GetTaggedClass          // p16 = class
    b   LGetIsaDone
// SUPPORT_TAGGED_POINTERS
#endif

LReturnZero:
    // x0 is already zero
    // Clear the register value
    mov x1, #0
    movi    d0, #0
    movi    d1, #0
    movi    d2, #0
    movi    d3, #0
    / / return
    ret

    END_ENTRY _objc_msgSend
Copy the code

The general process is as follows:

  • judge_objc_msgSendThe first parameterreceiverWhether it is empty, and determine whether it is supportedTaggedPointerstype
  • If the support andreceiver <= 0Go to theLNilOrTaggedjudgereceiverWhether it is empty. If it is empty, jumpLReturnZero, not for empty passGetTaggedClassgetp16 = class, and jump toLGetIsaDone
  • If we do not supportreceiverIf the value is empty, jumpLReturnZero.receiverIf it is not empty, go down and pass firstreceiverTo obtainisaAnd assign values to thep13And then through theGetClassFromIsa_p16The top16 = class
  • They all end up thereLGetIsaDoneGo,CacheLookupCache lookup process

CacheLookup

.macro CacheLookup Mode, Function, MissLabelDynamic, MissLabelConstant
        ...
        
	mov	x15, x16			// stash the original isa x15 = class
LLookupStart\Function:
	// p1 = SEL, p16 = isa
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS  // arm64 emulator
	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  / / arm64 real machine
        // CACHE : (2 * __SIZEOF_POINTER__)
        // [x16, #CACHE] = isa + 0x10 = CACHE address = _bucketsAndMaybeMask
	ldr	p11, [x16, #CACHE]			// p11 = mask|buckets p11 = _bucketsAndMaybeMask
#if CONFIG_USE_PREOPT_CACHES  / / arm64 real machine
#if __has_feature(ptrauth_calls)  // A12 chip above iPhone X
	tbnz	p11, #0, LLookupPreopt\Function // if the TBNZ test bit is not 0, jump. And TBZ correspondence
	and	p10, p11, #0x0000ffffffffffff	// p10 = buckets
#else
	and	p10, p11, #0x0000fffffffffffe	// p10 = buckets
	tbnz	p11, #0, LLookupPreopt\Function // if _bucketsAndMaybeMask is bit 0! =0, jump to LLookupPreopt\Function
#endif
	eor	p12, p1, p1, LSR #7             // eor (^) p12 = p1 ^(p1 >> 7)
        // p12 = p12&(_bucketsAndMaybeMask >> 48) = buckets
	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))
                                                // go through the search
						// do {
1:	ldp	p17, p9, [x13], #-BUCKET_SIZE	// {imp, sel} = *bucket
	cmp	p9, p1				// if (sel ! = _cmd)
	b.ne	3f				// Scan more Go to Step 3
						// } else {
2:	CacheHit \Mode				// HIT: Call or return IMP If a cache hit, imp is returned
						/ /}
3:	cbz	p9, \MissLabelDynamic		// if (sel == 0) goto Miss; _objc_MSgsend_uncached
	cmp	p13, p10			// } while (bucket >= buckets)
	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
	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

						// 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

...

.endmacro
Copy the code

The general process is as follows:

  • classMemory translation findcache.cacheThere arebucketandmask
  • By xor and shiftbucketThe subscriptindex
  • Take out thebucketIn theselAnd what comes in_cmdIf they are equal, a cache hit is returnedimp
  • Not equalbucketPan to loop through the search
  • If you can’t find it, go__objc_msgSend_uncached